diff --git a/docs/images/integrations/octopus-deploy/integrations-octopus-deploy-add-role.png b/docs/images/integrations/octopus-deploy/integrations-octopus-deploy-add-role.png index e929b3fb7..6af836355 100644 Binary files a/docs/images/integrations/octopus-deploy/integrations-octopus-deploy-add-role.png and b/docs/images/integrations/octopus-deploy/integrations-octopus-deploy-add-role.png differ diff --git a/docs/integrations/cicd/octopus-deploy.mdx b/docs/integrations/cicd/octopus-deploy.mdx index 6da2cf472..90f06e09a 100644 --- a/docs/integrations/cicd/octopus-deploy.mdx +++ b/docs/integrations/cicd/octopus-deploy.mdx @@ -29,12 +29,18 @@ Prerequisites:  + If you configure your access token to expire, + you will need to generate a new API key for Infisical prior to this date to keep your integration running. + Copy the generated **API Key** and click on the **Close** button.  + You can skip creating a new team if you already have an Octopus Deploy team configured with + the **Project Contributor** role to assign your Service Account to. + Navigate to **Configuration** > **Teams** and click on the **Add Team** button.  - On the **User Roles** tab, click on the **Include User Role** button, add the **Project Contributor** role and click on the **Apply** button. + On the **User Roles** tab, click on the **Include User Role** button, and add the **Project Contributor** role. Optionally, + click on the **Define Scope** button to further refine what projects your Service Account has access to. Click on the **Apply** button once complete.  Save your team changes by clicking on the **Save** button. diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index f61365c9d..e5f928158 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -19,7 +19,7 @@ import { Team, TeamCityBuildConfig, TGetIntegrationAuthOctopusDeployScopeValuesDTO, - TOctopusDeployScopeValues + TOctopusDeployVariableSetScopeValues } from "./types"; const integrationAuthKeys = { @@ -503,7 +503,7 @@ const fetchIntegrationAuthOctopusDeployScopeValues = async ({ spaceId, resourceId }: TGetIntegrationAuthOctopusDeployScopeValuesDTO) => { - const { data } = await apiRequest.get( + const { data } = await apiRequest.get( `/api/v1/integration-auth/${integrationAuthId}/octopus-deploy/scope-values`, { params: { scope, spaceId, resourceId } } ); @@ -799,14 +799,18 @@ export const useGetIntegrationAuthBitBucketWorkspaces = (integrationAuthId: stri export const useGetIntegrationAuthOctopusDeploySpaces = (integrationAuthId: string) => { return useQuery({ - queryKey: integrationAuthKeys.getIntegrationAuthBitBucketWorkspaces(integrationAuthId), + queryKey: integrationAuthKeys.getIntegrationAuthOctopusDeploySpaces(integrationAuthId), queryFn: () => fetchIntegrationAuthOctopusDeploySpaces(integrationAuthId) }); }; export const useGetIntegrationAuthOctopusDeployScopeValues = ( params: TGetIntegrationAuthOctopusDeployScopeValuesDTO, - options?: UseQueryOptions + options?: UseQueryOptions< + TOctopusDeployVariableSetScopeValues, + unknown, + TOctopusDeployVariableSetScopeValues + > ) => useQuery({ queryKey: integrationAuthKeys.getIntegrationAuthOctopusDeployScopeValues(params), diff --git a/frontend/src/hooks/api/integrationAuth/types.ts b/frontend/src/hooks/api/integrationAuth/types.ts index a2b22020d..58a643dff 100644 --- a/frontend/src/hooks/api/integrationAuth/types.ts +++ b/frontend/src/hooks/api/integrationAuth/types.ts @@ -112,7 +112,7 @@ export type TGetIntegrationAuthOctopusDeployScopeValuesDTO = { scope: OctopusDeployScope; }; -export type TOctopusDeployScopeValues = { +export type TOctopusDeployVariableSetScopeValues = { Environments: { Id: string; Name: string }[]; Machines: { Id: string; Name: string }[]; Actions: { Id: string; Name: string }[]; diff --git a/frontend/src/hooks/api/integrations/queries.tsx b/frontend/src/hooks/api/integrations/queries.tsx index c68e22197..11d42631a 100644 --- a/frontend/src/hooks/api/integrations/queries.tsx +++ b/frontend/src/hooks/api/integrations/queries.tsx @@ -4,7 +4,7 @@ import { createNotification } from "@app/components/notifications"; import { apiRequest } from "@app/config/request"; import { workspaceKeys } from "../workspace"; -import { TCloudIntegration, TIntegrationWithEnv } from "./types"; +import { TCloudIntegration, TIntegrationWithEnv, TOctopusDeployScopeValues } from "./types"; export const integrationQueryKeys = { getIntegrations: () => ["integrations"] as const, @@ -87,14 +87,7 @@ export const useCreateIntegration = () => { shouldMaskSecrets?: boolean; shouldProtectSecrets?: boolean; shouldEnableDelete?: boolean; - octopusDeployScopeValues?: { - Environment?: string[]; - Action?: string[]; - Channel?: string[]; - Machine?: string[]; - ProcessOwner?: string[]; - Role?: string[]; - }; + octopusDeployScopeValues?: TOctopusDeployScopeValues; }; }) => { const { diff --git a/frontend/src/hooks/api/integrations/types.ts b/frontend/src/hooks/api/integrations/types.ts index bfaa73884..8d4748cdf 100644 --- a/frontend/src/hooks/api/integrations/types.ts +++ b/frontend/src/hooks/api/integrations/types.ts @@ -57,9 +57,20 @@ export type TIntegration = { shouldMaskSecrets?: boolean; shouldProtectSecrets?: boolean; shouldEnableDelete?: boolean; + + octopusDeployScopeValues?: TOctopusDeployScopeValues; }; }; +export type TOctopusDeployScopeValues = { + Environment?: string[]; + Action?: string[]; + Channel?: string[]; + Machine?: string[]; + ProcessOwner?: string[]; + Role?: string[]; +}; + export type TIntegrationWithEnv = TIntegration & { environment: { id: string; diff --git a/frontend/src/pages/integrations/octopus-deploy/authorize.tsx b/frontend/src/pages/integrations/octopus-deploy/authorize.tsx index b07e9c159..bb78a93c2 100644 --- a/frontend/src/pages/integrations/octopus-deploy/authorize.tsx +++ b/frontend/src/pages/integrations/octopus-deploy/authorize.tsx @@ -69,7 +69,7 @@ export default function OctopusDeployIntegrationPage() { src="/images/integrations/Octopus Deploy.png" height={30} width={30} - alt="Databricks logo" + alt="Octopus Deploy logo" /> Octopus Deploy Integration diff --git a/frontend/src/pages/integrations/octopus-deploy/create.tsx b/frontend/src/pages/integrations/octopus-deploy/create.tsx index 60f8503c0..cc918d67c 100644 --- a/frontend/src/pages/integrations/octopus-deploy/create.tsx +++ b/frontend/src/pages/integrations/octopus-deploy/create.tsx @@ -12,9 +12,9 @@ import { CardTitle, FilterableSelect, FormControl, - Input, Spinner } from "@app/components/v2"; +import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { useWorkspace } from "@app/context"; import { useCreateIntegration, useGetIntegrationAuthApps } from "@app/hooks/api"; import { @@ -63,6 +63,7 @@ export default function OctopusDeployCreateIntegrationPage() { const currentSpace = watch("targetSpace", octopusDeploySpaces?.[0]); const currentScope = watch("scope"); + const sourceEnv = watch("sourceEnvironment"); const { data: octopusDeployResources, isLoading: isOctopusDeployResourcesLoading } = useGetIntegrationAuthApps( @@ -195,11 +196,11 @@ export default function OctopusDeployCreateIntegrationPage() { name="secretPath" render={({ field: { value, onChange }, fieldState: { error } }) => ( - )} diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationConnectionSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationConnectionSection.tsx index 8187801c8..778cdd00a 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationConnectionSection.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationConnectionSection.tsx @@ -55,6 +55,8 @@ export const IntegrationConnectionSection = ({ integration }: Props) => { return "Path"; case "bitbucket": return "Repository"; + case "octopus-deploy": + return "Project"; case "github": if (["github-env", "github-repo"].includes(integration.scope!)) { return "Repository"; @@ -104,6 +106,16 @@ export const IntegrationConnectionSection = ({ integration }: Props) => { ); } + + if (integration.integration === "octopus-deploy") { + return ( + + + {integration.targetEnvironment} + + ); + } + if ( ["vercel", "netlify", "railway", "gitlab", "teamcity"].includes(integration.integration) || (integration.integration === "github" && integration.scope === "github-env") diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx index 653204455..9778c72a9 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/IntegrationSettingsSection.tsx @@ -1,4 +1,5 @@ import { TIntegrationWithEnv } from "@app/hooks/api/integrations/types"; +import { OctopusDeployScopeValues } from "@app/views/IntegrationsPage/IntegrationDetailsPage/components/OctopusDeployScopeValues"; type Props = { integration: TIntegrationWithEnv; @@ -26,13 +27,17 @@ const metadataMappings: Record { const renderValue = (key: K, value: MetadataValue) => { if (!value) return null; + if (key === "octopusDeployScopeValues") + return ; + // If it's a boolean, we render a generic "Yes" or "No" response. if (typeof value === "boolean") { return value ? "Yes" : "No"; @@ -49,7 +54,7 @@ export const IntegrationSettingsSection = ({ integration }: Props) => { } if (key === "githubVisibilityRepoIds") { - return value.join(", "); + return (value as string[]).join(", "); } } diff --git a/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/OctopusDeployScopeValues.tsx b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/OctopusDeployScopeValues.tsx new file mode 100644 index 000000000..d697e7725 --- /dev/null +++ b/frontend/src/views/IntegrationsPage/IntegrationDetailsPage/components/OctopusDeployScopeValues.tsx @@ -0,0 +1,90 @@ +import { FormLabel, Spinner } from "@app/components/v2"; +import { useGetIntegrationAuthOctopusDeployScopeValues } from "@app/hooks/api/integrationAuth/queries"; +import { + OctopusDeployScope, + TOctopusDeployVariableSetScopeValues +} from "@app/hooks/api/integrationAuth/types"; +import { TIntegration, TOctopusDeployScopeValues } from "@app/hooks/api/integrations/types"; + +type OctopusDeployScopeValuesProps = { + integration: TIntegration; +}; + +// remove plural since Octopus Deploy can decide whether they want to use singular or plural... +const modifyKey = (key: keyof TOctopusDeployVariableSetScopeValues) => { + switch (key) { + case "Processes": + return "ProcessOwner"; + default: + return key.substring(0, key.length - 1); + } +}; + +export const OctopusDeployScopeValues = ({ integration }: OctopusDeployScopeValuesProps) => { + const hasScopeValues = Boolean( + Object.values(integration.metadata?.octopusDeployScopeValues ?? {}).some( + (values) => values.length > 0 + ) + ); + + const { data: scopeValues = {}, isLoading } = useGetIntegrationAuthOctopusDeployScopeValues( + { + scope: OctopusDeployScope.Project, + spaceId: integration.targetEnvironmentId!, + resourceId: integration.appId!, + integrationAuthId: integration.integrationAuthId + }, + { + enabled: hasScopeValues + } + ); + + if (!integration.metadata?.octopusDeployScopeValues) + return Not Configured; + + if (isLoading) return ; + + const scopeValuesMap = new Map( + Object.entries(scopeValues).map(([key, values]) => [ + modifyKey(key as keyof TOctopusDeployVariableSetScopeValues), + new Map((values as { Name: string; Id: string }[]).map((value) => [value.Id, value.Name])) + ]) + ); + + return ( + <> + {Object.entries(integration.metadata.octopusDeployScopeValues).map(([key, values]) => { + if (!values.length) return null; + + const getLabel = (scope: string) => { + switch (scope as keyof TOctopusDeployScopeValues) { + case "Role": + return "Target Tags"; + case "Machine": + return "Targets"; + case "ProcessOwner": + return "Processes"; + case "Action": + return "Steps"; + default: + return `${scope}s`; + } + }; + + return ( + + + + + {values + .map((value) => scopeValuesMap.get(key)?.get(value)!) + .map((name) => ( + {name} + ))} + + + ); + })} + > + ); +};
{name}