Skip to content

Commit

Permalink
fix: tfc Scanner
Browse files Browse the repository at this point in the history
  • Loading branch information
zacharyblasczyk committed Nov 2, 2024
1 parent 22f68a9 commit 0d6abae
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 106 deletions.
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);
});
});
53 changes: 17 additions & 36 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 Down Expand Up @@ -78,12 +84,7 @@ export async function scan() {

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

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

logger.info("Successfully registered targets");
} catch (error) {
Expand Down Expand Up @@ -155,23 +156,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.

0 comments on commit 0d6abae

Please sign in to comment.