Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: tfc Scanner #195

Merged
merged 2 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions integrations/kubernetes-job-agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const deployManifest = async (
updateJobRequest: {
status: "invalid_job_agent",
message: "Job name not found in manifest.",
},
});
return;
}
Expand All @@ -51,7 +50,6 @@ const deployManifest = async (
status: "in_progress",
externalId: `${namespace}/${name}`,
message: "Job created successfully.",
},
});
logger.info(`Job created successfully`, {
jobId,
Expand Down
132 changes: 83 additions & 49 deletions integrations/terraform-cloud-scanner/src/__tests__/scanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,25 @@ describe("Scanner Module", () => {
vi.spyOn(env, "CTRLPLANE_SCANNER_NAME", "get").mockReturnValue(
"mock-scanner",
);
vi.spyOn(env, "CTRLPLANE_WORKSPACE_TARGET_NAME", "get").mockReturnValue(
"{{workspace.attributes.name}}",
);

const mockWorkspaces = [
{
id: "workspace-1",
type: "workspaces",
attributes: { name: "Workspace-One", "tag-names": ["prod"] },
attributes: {
name: "Workspace-One",
"tag-names": ["prod", "env:staging"],
"auto-apply": true,
"terraform-version": "1.0.0",
"vcs-repo": {
identifier: "org/repo",
branch: "main",
"repository-http-url": "https://github.com/org/repo",
},
},
},
];

Expand All @@ -67,79 +80,100 @@ describe("Scanner Module", () => {
sensitive: false,
},
},
{
id: "var-2",
type: "vars",
attributes: {
key: "ENV_VAR",
value: "env_value",
category: "env",
hcl: false,
sensitive: false,
},
},
];

const mockProviderId = "provider-123";

vi.mocked(listWorkspaces).mockResolvedValue(mockWorkspaces as any);
vi.mocked(listVariables).mockResolvedValue(mockVariables as any);

vi.spyOn(api, "setTargetProvidersTargets").mockResolvedValue(undefined);
vi.spyOn(api, "upsertTargetProvider").mockResolvedValue({
id: mockProviderId,
name: "mock-provider-name",
workspaceId: "ctrlplane-workspace",
vi.spyOn(api, "GET").mockResolvedValue({
data: {
id: "provider-123",
name: "mock-provider-name",
workspaceId: "36427c59-e2bd-4b3f-bf54-54404ef6aa0e",
},
status: 200,
statusText: "OK",
headers: {},
config: {} as any,
});

const patchMock = vi.spyOn(api, "PATCH").mockResolvedValue({
data: {
"application/json": {
id: "mock-id",
name: "mock-name",
workspaceId: "mock-workspace-id",
kind: "mock-kind",
identifier: "mock-identifier",
version: "mock-version",
config: {},
metadata: {},
},
},
response: new Response(),
});

await scan();

expect(() => listVariables("workspace-1")).not.toThrow();
expect(() =>
api.upsertTargetProvider({
workspaceId: "ctrlplane-workspace",
name: "mock-scanner",
}),
).not.toThrow();

expect(() => listWorkspaces()).not.toThrow();
expect(() => listVariables("workspace-1")).not.toThrow();
expect(() =>
api.upsertTargetProvider({
workspaceId: "ctrlplane-workspace",
name: "mock-scanner",
}),
).not.toThrow();
expect(listWorkspaces).toHaveBeenCalled();
expect(listVariables).toHaveBeenCalledWith("workspace-1");

expect(() =>
api.setTargetProvidersTargets({
providerId: mockProviderId,
setTargetProvidersTargetsRequest: {
expect(patchMock).toHaveBeenCalledWith(
"/v1/target-providers/{providerId}/set",
expect.objectContaining({
body: {
targets: [
{
version: "terraform/v1",
kind: "Workspace",
name: "workspace-Workspace-One",
name: "mock-workspace-target-name",
identifier: "workspace-1",
config: {
workspaceId: "workspace-1",
},
metadata: {
"terraform/organization": "mock-org",
"terraform/workspace-name": "Workspace-One",
"var/TF_VAR_example": "example_value",
"env/ENV_VAR": "env_value",
"tags/prod": "true",
"ctrlplane/link": expect.stringContaining(
"https://app.terraform.io/app/mock-org/workspaces/Workspace-One",
),
"ctrlplane/external-id": "workspace-1",
"ctrlplane/links":
'{"Terraform Workspace":"https://app.terraform.io/app/mock-org/workspaces/Workspace-One"}',
"terraform-cloud/organization": "mock-org",
"terraform-cloud/tag/env": "staging",
"terraform-cloud/tag/prod": "true",
"terraform-cloud/variables/TF_VAR_example": "example_value",
"terraform-cloud/vcs-repo/branch": "main",
"terraform-cloud/vcs-repo/identifier": "org/repo",
"terraform-cloud/vcs-repo/repository-http-url":
"https://github.com/org/repo",
"terraform-cloud/workspace-auto-apply": "true",
"terraform-cloud/workspace-name": "Workspace-One",
"terraform/version": "1.0.0",
},
},
],
},
params: {
path: {
providerId: "provider-123",
},
},
}),
).not.toThrow();
);

expect(logger.info).toHaveBeenCalledWith("Successfully registered targets");
});

it("should handle scan errors gracefully", async () => {
vi.mocked(listWorkspaces).mockRejectedValue(new Error("API Error"));

const mockExit = vi
.spyOn(process, "exit")
.mockImplementation(() => undefined as never);

await scan();

expect(logger.error).toHaveBeenCalledWith(
"An error occurred during the scan process:",
expect.any(Error),
);
expect(mockExit).toHaveBeenCalledWith(1);
});
});
57 changes: 18 additions & 39 deletions integrations/terraform-cloud-scanner/src/scanner.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { SetTargetProvidersTargetsRequestTargetsInner } from "@ctrlplane/node-sdk";
import handlebars from "handlebars";
import _ from "lodash";

import { logger } from "@ctrlplane/logger";
import { TargetProvider } from "@ctrlplane/node-sdk";

import type { Variable, Workspace } from "./types.js";
import { listVariables, listWorkspaces } from "./api.js";
Expand All @@ -17,21 +17,27 @@ const workspaceTemplate = handlebars.compile(
* Scans Terraform Cloud workspaces and registers them as targets with prefixed labels and a link.
*/
export async function scan() {
const scanner = new TargetProvider(
{
workspaceId: env.CTRLPLANE_WORKSPACE_ID,
name: env.CTRLPLANE_SCANNER_NAME,
},
api,
);
logger.info("Starting Terraform Cloud scan");

try {
const providerId = await getOrCreateProviderId();
if (!providerId) {
logger.error(
"Provider ID is not available. Aborting target registration.",
);
process.exit(1);
}
const provider = await scanner.get();

logger.info(`Scanner ID: ${provider.id}`, { id: provider.id });
logger.info("Running Terrafrom Cloud scanner", {
date: new Date().toISOString(),
});

const workspaces: Workspace[] = await listWorkspaces();
logger.info(`Found ${workspaces.length} workspaces`);

const targets: SetTargetProvidersTargetsRequestTargetsInner[] = [];
const targets = [];

for (const workspace of workspaces) {
logger.info(
Expand All @@ -48,7 +54,7 @@ export async function scan() {
const link = buildWorkspaceLink(workspace);
const targetName = workspaceTemplate({ workspace });

const target: SetTargetProvidersTargetsRequestTargetsInner = {
const target = {
version: "terraform/v1",
kind: "Workspace",
name: targetName,
Expand All @@ -74,16 +80,9 @@ export async function scan() {
targets.push(target);
}

const uniqueTargets = _.uniqBy(targets, (t) => t.identifier);

logger.info(`Registering ${uniqueTargets.length} unique targets`);
logger.info(`Registering ${targets.length} unique targets`);

await api.setTargetProvidersTargets({
providerId,
setTargetProvidersTargetsRequest: {
targets: uniqueTargets,
},
});
await scanner.set(targets);

logger.info("Successfully registered targets");
} catch (error) {
Expand Down Expand Up @@ -155,23 +154,3 @@ function buildWorkspaceLink(workspace: Workspace): Record<string, string> {
)}/workspaces/${encodeURIComponent(workspace.attributes.name)}`,
};
}

/**
* Helper function to get or create the provider ID.
* @returns The provider ID as a string or null if failed.
*/
async function getOrCreateProviderId(): Promise<string | null> {
return api
.upsertTargetProvider({
workspaceId: env.CTRLPLANE_WORKSPACE_ID,
name: env.CTRLPLANE_SCANNER_NAME,
})
.then(({ id }) => {
logger.info(`Using provider ID: ${id}`);
return id;
})
.catch((error) => {
logger.error("Failed to get or create provider ID:", error);
return null;
});
}
8 changes: 3 additions & 5 deletions integrations/terraform-cloud-scanner/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Configuration, DefaultApi } from "@ctrlplane/node-sdk";
import { createClient } from "@ctrlplane/node-sdk";

import { env } from "./config.js";

const config = new Configuration({
basePath: `${env.CTRLPLANE_BASE_URL}/api`,
export const api = createClient({
baseUrl: env.CTRLPLANE_BASE_URL,
apiKey: env.CTRLPLANE_API_KEY,
});

export const api = new DefaultApi(config);
14 changes: 0 additions & 14 deletions packages/node-sdk/openapitools.json

This file was deleted.

Loading