review fixes

This commit is contained in:
x
2025-04-24 23:30:35 -04:00
parent 0882c181d0
commit 7c49f6e302
8 changed files with 85 additions and 55 deletions

View File

@@ -3,6 +3,7 @@ import { AxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { TeamCityConnectionMethod } from "./teamcity-connection-enums";
@@ -12,6 +13,14 @@ import {
TTeamCityListProjectsResponse
} from "./teamcity-connection-types";
export const getTeamCityInstanceUrl = async (config: TTeamCityConnectionConfig) => {
const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
await blockLocalAndPrivateIpAddresses(instanceUrl);
return instanceUrl;
};
export const getTeamCityConnectionListItem = () => {
return {
name: "TeamCity" as const,
@@ -21,7 +30,8 @@ export const getTeamCityConnectionListItem = () => {
};
export const validateTeamCityConnectionCredentials = async (config: TTeamCityConnectionConfig) => {
const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
const instanceUrl = await getTeamCityInstanceUrl(config);
const { accessToken } = config.credentials;
try {
@@ -46,7 +56,7 @@ export const validateTeamCityConnectionCredentials = async (config: TTeamCityCon
};
export const listTeamCityProjects = async (appConnection: TTeamCityConnection) => {
const instanceUrl = removeTrailingSlash(appConnection.credentials.instanceUrl);
const instanceUrl = await getTeamCityInstanceUrl(appConnection);
const { accessToken } = appConnection.credentials;
const resp = await request.get<TTeamCityListProjectsResponse>(

View File

@@ -145,7 +145,9 @@ export const SecretSyncFns = {
case SecretSync.TeamCity:
return TeamCitySyncFns.syncSecrets(secretSync, secretMap);
default:
throw new Error(`Unhandled sync destination for sync secrets fns: ${secretSync.destination}`);
throw new Error(
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
);
}
},
getSecrets: async (
@@ -205,7 +207,9 @@ export const SecretSyncFns = {
secretMap = await TeamCitySyncFns.getSecrets(secretSync);
break;
default:
throw new Error(`Unhandled sync destination for get secrets fns: ${secretSync.destination}`);
throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
);
}
return secretMap;
@@ -258,7 +262,9 @@ export const SecretSyncFns = {
case SecretSync.TeamCity:
return TeamCitySyncFns.removeSecrets(secretSync, secretMap);
default:
throw new Error(`Unhandled sync destination for remove secrets fns: ${secretSync.destination}`);
throw new Error(
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
);
}
}
};

View File

@@ -1,5 +1,5 @@
import { request } from "@app/lib/config/request";
import { removeTrailingSlash } from "@app/lib/fn";
import { getTeamCityInstanceUrl } from "@app/services/app-connection/teamcity";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import {
@@ -89,7 +89,7 @@ export const TeamCitySyncFns = {
destinationConfig: { project, buildConfig }
} = secretSync;
const instanceUrl = removeTrailingSlash(connection.credentials.instanceUrl);
const instanceUrl = await getTeamCityInstanceUrl(connection);
const { accessToken } = connection.credentials;
for await (const entry of Object.entries(secretMap)) {
@@ -145,7 +145,7 @@ export const TeamCitySyncFns = {
destinationConfig: { project, buildConfig }
} = secretSync;
const instanceUrl = removeTrailingSlash(connection.credentials.instanceUrl);
const instanceUrl = await getTeamCityInstanceUrl(connection);
const { accessToken } = connection.credentials;
const variables = await listTeamCityVariables({ instanceUrl, accessToken, project, buildConfig });
@@ -175,7 +175,7 @@ export const TeamCitySyncFns = {
destinationConfig: { project, buildConfig }
} = secretSync;
const instanceUrl = removeTrailingSlash(connection.credentials.instanceUrl);
const instanceUrl = await getTeamCityInstanceUrl(connection);
const { accessToken } = connection.credentials;
return listTeamCityVariables({ instanceUrl, accessToken, project, buildConfig });

View File

@@ -18,17 +18,28 @@ Infisical supports connecting to TeamCity using an Access Token to securely sync
</Step>
<Step title="Create the Access Token">
Click the **Create access token** button and provide a name for your token (e.g., "Infisical Integration"). You may set an expiration date or leave it blank for no expiry.
The permission scope can either be **Same as current user** or **Limit per project**. If you're choosing **Limit per project**, make sure you select the relevant project and enable the **View build configuration settings** and **Edit project** permissions.
The permission scope can either be **Same as current user** or **Limit per project**.
If you're choosing **Limit per project**, make sure you select the relevant project and enable the permissions relevant to your use case:
<Tabs>
<Tab title="Secret Sync Permissions">
- View build configuration settings
- Edit project
</Tab>
</Tabs>
![TeamCity Token Popup](/images/app-connections/teamcity/teamcity-token-popup.png)
<Note>
Setting your permission scope to **Same as current user** will allow your integration to access multiple projects as long as the current user has read and write access to them.
</Note>
<Note>
Setting your permission scope to **Limit per project** requires the **View build configuration settings** and **Edit project** permissions.
If you configure an expiry date for your access token, you must manually rotate to a new token before the expiration date to prevent service interruption.
</Note>
![TeamCity Token Popup](/images/app-connections/teamcity/teamcity-token-popup.png)
</Step>
<Step title="Copy the Access Token">
After creation, a modal with the API token will be displayed. Copy this token immediately and store it securely, as you won't be able to view it again after closing this dialog.
After creation, a modal with the Access Token will be displayed. Copy this token immediately and store it securely, as you won't be able to view it again after closing this dialog.
![TeamCity Token Copy Popup](/images/app-connections/teamcity/teamcity-token-copy.png)
</Step>
<Step title="Token Created">

View File

@@ -434,10 +434,10 @@
"integrations/app-connections/humanitec",
"integrations/app-connections/mssql",
"integrations/app-connections/postgres",
"integrations/app-connections/teamcity",
"integrations/app-connections/terraform-cloud",
"integrations/app-connections/vercel",
"integrations/app-connections/windmill",
"integrations/app-connections/teamcity"
"integrations/app-connections/windmill"
]
}
]
@@ -458,10 +458,10 @@
"integrations/secret-syncs/gcp-secret-manager",
"integrations/secret-syncs/github",
"integrations/secret-syncs/humanitec",
"integrations/secret-syncs/teamcity",
"integrations/secret-syncs/terraform-cloud",
"integrations/secret-syncs/vercel",
"integrations/secret-syncs/windmill",
"integrations/secret-syncs/teamcity"
"integrations/secret-syncs/windmill"
]
}
]
@@ -1045,6 +1045,18 @@
"api-reference/endpoints/app-connections/postgres/delete"
]
},
{
"group": "TeamCity",
"pages": [
"api-reference/endpoints/app-connections/teamcity/list",
"api-reference/endpoints/app-connections/teamcity/available",
"api-reference/endpoints/app-connections/teamcity/get-by-id",
"api-reference/endpoints/app-connections/teamcity/get-by-name",
"api-reference/endpoints/app-connections/teamcity/create",
"api-reference/endpoints/app-connections/teamcity/update",
"api-reference/endpoints/app-connections/teamcity/delete"
]
},
{
"group": "Terraform Cloud",
"pages": [
@@ -1080,18 +1092,6 @@
"api-reference/endpoints/app-connections/windmill/update",
"api-reference/endpoints/app-connections/windmill/delete"
]
},
{
"group": "TeamCity",
"pages": [
"api-reference/endpoints/app-connections/teamcity/list",
"api-reference/endpoints/app-connections/teamcity/available",
"api-reference/endpoints/app-connections/teamcity/get-by-id",
"api-reference/endpoints/app-connections/teamcity/get-by-name",
"api-reference/endpoints/app-connections/teamcity/create",
"api-reference/endpoints/app-connections/teamcity/update",
"api-reference/endpoints/app-connections/teamcity/delete"
]
}
]
},
@@ -1222,6 +1222,20 @@
"api-reference/endpoints/secret-syncs/humanitec/remove-secrets"
]
},
{
"group": "TeamCity",
"pages": [
"api-reference/endpoints/secret-syncs/teamcity/list",
"api-reference/endpoints/secret-syncs/teamcity/get-by-id",
"api-reference/endpoints/secret-syncs/teamcity/get-by-name",
"api-reference/endpoints/secret-syncs/teamcity/create",
"api-reference/endpoints/secret-syncs/teamcity/update",
"api-reference/endpoints/secret-syncs/teamcity/delete",
"api-reference/endpoints/secret-syncs/teamcity/sync-secrets",
"api-reference/endpoints/secret-syncs/teamcity/import-secrets",
"api-reference/endpoints/secret-syncs/teamcity/remove-secrets"
]
},
{
"group": "Terraform Cloud",
"pages": [
@@ -1262,20 +1276,6 @@
"api-reference/endpoints/secret-syncs/windmill/import-secrets",
"api-reference/endpoints/secret-syncs/windmill/remove-secrets"
]
},
{
"group": "TeamCity",
"pages": [
"api-reference/endpoints/secret-syncs/teamcity/list",
"api-reference/endpoints/secret-syncs/teamcity/get-by-id",
"api-reference/endpoints/secret-syncs/teamcity/get-by-name",
"api-reference/endpoints/secret-syncs/teamcity/create",
"api-reference/endpoints/secret-syncs/teamcity/update",
"api-reference/endpoints/secret-syncs/teamcity/delete",
"api-reference/endpoints/secret-syncs/teamcity/sync-secrets",
"api-reference/endpoints/secret-syncs/teamcity/import-secrets",
"api-reference/endpoints/secret-syncs/teamcity/remove-secrets"
]
}
]
},

View File

@@ -90,9 +90,15 @@ export const TeamCitySyncFields = () => {
isOptional
label="Build Configuration"
helperText={
<span>
Not selecting a Build Configuration will sync your secrets with the entire project.
</span>
<Tooltip
className="max-w-md"
content='Ensure the configuration exists in the selected project and that your Access Token has the "View build configuration settings" permission.'
>
<div>
<span>Don&#39;t see the configuration you&#39;re looking for?</span>{" "}
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
</div>
</Tooltip>
}
>
<FilterableSelect
@@ -113,6 +119,10 @@ export const TeamCitySyncFields = () => {
</FormControl>
)}
/>
<span className="text-sm text-bunker-300">
Not selecting a Build Configuration will sync your secrets to the entire project.
</span>
</>
);
};

View File

@@ -9,6 +9,6 @@ export type TTeamCityConnection = TRootAppConnection & { app: AppConnection.Team
method: TeamCityConnectionMethod.AccessToken;
credentials: {
accessToken: string;
instanceUrl?: string;
instanceUrl: string;
};
};

View File

@@ -34,14 +34,7 @@ const formSchema = z.discriminatedUnion("method", [
method: z.literal(TeamCityConnectionMethod.AccessToken),
credentials: z.object({
accessToken: z.string().trim().min(1, "Access Token required"),
instanceUrl: z
.string()
.trim()
.transform((value) => value || undefined)
.refine((value) => (!value ? true : z.string().url().safeParse(value).success), {
message: "Invalid instance URL"
})
.optional()
instanceUrl: z.string().trim().url("Invalid Instance URL")
})
})
]);