feat: finished integration sync for rundeck

This commit is contained in:
Sheen Capadngan
2024-06-04 21:10:24 +08:00
parent 2a538d9560
commit 378d6c259b
11 changed files with 96 additions and 5 deletions

View File

@@ -661,6 +661,7 @@ export const INTEGRATION = {
targetServiceId:
"The service based grouping identifier ID of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank",
owner: "External integration providers service entity owner. Used in Github.",
url: "The self-hosted URL of the platform to integrate with",
path: "Path to save the synced secrets. Used by Gitlab, AWS Parameter Store, Vault",
region: "AWS region to sync secrets to.",
scope: "Scope of the provider. Used by Github, Qovery",

View File

@@ -42,6 +42,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
targetService: z.string().trim().optional().describe(INTEGRATION.CREATE.targetService),
targetServiceId: z.string().trim().optional().describe(INTEGRATION.CREATE.targetServiceId),
owner: z.string().trim().optional().describe(INTEGRATION.CREATE.owner),
url: z.string().trim().optional().describe(INTEGRATION.CREATE.url),
path: z.string().trim().optional().describe(INTEGRATION.CREATE.path),
region: z.string().trim().optional().describe(INTEGRATION.CREATE.region),
scope: z.string().trim().optional().describe(INTEGRATION.CREATE.scope),

View File

@@ -199,6 +199,7 @@ export const integrationAuthServiceFactory = ({
projectId,
namespace,
integration,
url,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8,
...(integration === Integrations.GCP_SECRET_MANAGER

View File

@@ -3355,6 +3355,74 @@ const syncSecretsHasuraCloud = async ({
}
};
/** Sync/push [secrets] to Rundeck
* @param {Object} obj
* @param {TIntegrations} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for Rundeck integration
*/
const syncSecretsRundeck = async ({
integration,
secrets,
accessToken
}: {
integration: TIntegrations;
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
}) => {
interface RundeckSecretResource {
name: string;
}
interface RundeckSecretsGetRes {
resources: RundeckSecretResource[];
}
let existingRundeckSecrets: string[] = [];
try {
const listResult = await request.get<RundeckSecretsGetRes>(
`${integration.url}/api/44/storage/${integration.path}`,
{
headers: {
"X-Rundeck-Auth-Token": accessToken
}
}
);
existingRundeckSecrets = listResult.data.resources.map((res) => res.name);
} catch (err) {
logger.info("No existing rundeck secrets");
}
for await (const [key, value] of Object.entries(secrets)) {
if (existingRundeckSecrets.includes(key)) {
await request.put(`${integration.url}/api/44/storage/${integration.path}/${key}`, value, {
headers: {
"X-Rundeck-Auth-Token": accessToken,
"Content-Type": "application/x-rundeck-data-password"
}
});
} else {
await request.post(`${integration.url}/api/44/storage/${integration.path}/${key}`, value, {
headers: {
"X-Rundeck-Auth-Token": accessToken,
"Content-Type": "application/x-rundeck-data-password"
}
});
}
}
for await (const existingSecret of existingRundeckSecrets) {
if (!(existingSecret in secrets)) {
await request.delete(`${integration.url}/api/44/storage/${integration.path}/${existingSecret}`, {
headers: {
"X-Rundeck-Auth-Token": accessToken
}
});
}
}
};
/**
* Sync/push [secrets] to [app] in integration named [integration]
*
@@ -3621,6 +3689,13 @@ export const syncIntegrationSecrets = async ({
accessToken
});
break;
case Integrations.RUNDECK:
await syncSecretsRundeck({
integration,
secrets,
accessToken
});
break;
default:
throw new BadRequestError({ message: "Invalid integration" });
}

View File

@@ -43,6 +43,7 @@ export const integrationServiceFactory = ({
scope,
actorId,
region,
url,
isActive,
metadata,
secretPath,
@@ -87,6 +88,7 @@ export const integrationServiceFactory = ({
region,
scope,
owner,
url,
appId,
path,
app,

View File

@@ -12,6 +12,7 @@ export type TCreateIntegrationDTO = {
targetService?: string;
targetServiceId?: string;
owner?: string;
url?: string;
path?: string;
region?: string;
scope?: string;

View File

@@ -32,7 +32,8 @@ const integrationSlugNameMapping: Mapping = {
northflank: "Northflank",
windmill: "Windmill",
"gcp-secret-manager": "GCP Secret Manager",
"hasura-cloud": "Hasura Cloud"
"hasura-cloud": "Hasura Cloud",
rundeck: "Rundeck"
};
const envMapping: Mapping = {

View File

@@ -7,6 +7,7 @@ export type IntegrationAuth = {
updatedAt: string;
algorithm: string;
keyEncoding: string;
url?: string;
teamId?: string;
};

View File

@@ -41,6 +41,7 @@ export const useCreateIntegration = () => {
owner,
path,
region,
url,
scope,
secretPath,
metadata
@@ -56,6 +57,7 @@ export const useCreateIntegration = () => {
targetService?: string;
targetServiceId?: string;
owner?: string;
url?: string;
path?: string;
region?: string;
scope?: string;
@@ -85,6 +87,7 @@ export const useCreateIntegration = () => {
targetEnvironmentId,
targetService,
targetServiceId,
url,
owner,
path,
scope,

View File

@@ -38,7 +38,10 @@ export default function RundeckCreateIntegrationPage() {
watch,
formState: { isSubmitting }
} = useForm<TFormSchema>({
resolver: zodResolver(schema)
resolver: zodResolver(schema),
defaultValues: {
secretPath: "/"
}
});
const router = useRouter();
const { mutateAsync } = useCreateIntegration();
@@ -60,6 +63,7 @@ export default function RundeckCreateIntegrationPage() {
isActive: true,
path: keyStoragePath,
sourceEnvironment,
url: integrationAuth.url,
secretPath
});
@@ -158,7 +162,7 @@ export default function RundeckCreateIntegrationPage() {
placeholder={`keys/project/${workspace.name
.toLowerCase()
.replace(/ /g, "-")}/${selectedSourceEnvironment}`}
value={field.value}
{...field}
/>
</FormControl>
)}

View File

@@ -141,7 +141,8 @@ export const IntegrationsSection = ({
label={
(integration.integration === "qovery" && integration?.scope) ||
(integration.integration === "aws-secret-manager" && "Secret") ||
(integration.integration === "aws-parameter-store" && "Path") ||
(["aws-parameter-store", "rundeck"].includes(integration.integration) &&
"Path") ||
(integration?.integration === "terraform-cloud" && "Project") ||
(integration?.scope === "github-org" && "Organization") ||
(["github-repo", "github-env"].includes(integration?.scope as string) &&
@@ -153,7 +154,7 @@ export const IntegrationsSection = ({
{(integration.integration === "hashicorp-vault" &&
`${integration.app} - path: ${integration.path}`) ||
(integration.scope === "github-org" && `${integration.owner}`) ||
(integration.integration === "aws-parameter-store" &&
(["aws-parameter-store", "rundeck"].includes(integration.integration) &&
`${integration.path}`) ||
(integration.scope?.startsWith("github-") &&
`${integration.owner}/${integration.app}`) ||