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

refactor(terraform): don't init in status handler #6825

Merged
merged 2 commits into from
Feb 8, 2025
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
10 changes: 5 additions & 5 deletions plugins/terraform/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { terraform } from "./cli.js"
import type { TerraformProvider } from "./provider.js"
import { ConfigurationError, ParameterError } from "@garden-io/sdk/build/src/exceptions.js"
import { prepareVariables, setWorkspace, tfValidate } from "./helpers.js"
import { prepareVariables, ensureWorkspace, initTerraform } from "./helpers.js"
import type { ConfigGraph, PluginCommand, PluginCommandParams } from "@garden-io/sdk/build/src/types.js"
import { join } from "path"
import fsExtra from "fs-extra"
Expand Down Expand Up @@ -52,8 +52,8 @@ function makeRootCommand(commandName: string): PluginCommand {
const root = join(ctx.projectRoot, provider.config.initRoot)
const workspace = provider.config.workspace || null

await setWorkspace({ ctx, provider, root, log, workspace })
await tfValidate({ ctx, provider, root, log })
await ensureWorkspace({ ctx, provider, root, log, workspace })
await initTerraform({ ctx, provider, root, log })

args = [commandName, ...(await prepareVariables(root, provider.config.variables)), ...args]

Expand Down Expand Up @@ -95,8 +95,8 @@ function makeActionCommand(commandName: string): PluginCommand {
const provider = ctx.provider as TerraformProvider
const workspace = spec.workspace || null

await setWorkspace({ ctx, provider, root, log, workspace })
await tfValidate({ ctx, provider, root, log })
await ensureWorkspace({ ctx, provider, root, log, workspace })
await initTerraform({ ctx, provider, root, log })

args = [commandName, ...(await prepareVariables(root, spec.variables)), ...args.slice(1)]
await terraform(ctx, provider).spawnAndWait({
Expand Down
15 changes: 12 additions & 3 deletions plugins/terraform/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
import { join } from "path"
import { deline } from "@garden-io/core/build/src/util/string.js"
import { terraform } from "./cli.js"
import { applyStack, getStackStatus, getTfOutputs, prepareVariables, setWorkspace } from "./helpers.js"
import {
applyStack,
getStackStatus,
getTfOutputs,
prepareVariables,
ensureWorkspace,
initTerraform,
} from "./helpers.js"
import type { TerraformProvider } from "./provider.js"
import type { DeployActionHandler } from "@garden-io/core/build/src/plugin/action-types.js"
import type { DeployState } from "@garden-io/core/build/src/types/service.js"
Expand All @@ -25,6 +32,8 @@ export const getTerraformStatus: DeployActionHandler<"getStatus", TerraformDeplo
const variables = spec.variables
const workspace = spec.workspace || null

await ensureWorkspace({ log, ctx, provider, root, workspace })
await initTerraform({ log, ctx, provider, root })
const status = await getStackStatus({
ctx,
log,
Expand Down Expand Up @@ -65,7 +74,7 @@ export const deployTerraform: DeployActionHandler<"deploy", TerraformDeploy> = a
`
)
)
await setWorkspace({ log, ctx, provider, root, workspace })
await ensureWorkspace({ log, ctx, provider, root, workspace })
}

return {
Expand Down Expand Up @@ -99,7 +108,7 @@ export const deleteTerraformModule: DeployActionHandler<"delete", TerraformDeplo
const variables = spec.variables
const workspace = spec.workspace || null

await setWorkspace({ ctx, provider, root, log, workspace })
await ensureWorkspace({ ctx, provider, root, log, workspace })

const args = ["destroy", "-auto-approve", "-input=false", ...(await prepareVariables(root, variables))]
await terraform(ctx, provider).exec({ log, args, cwd: root })
Expand Down
19 changes: 8 additions & 11 deletions plugins/terraform/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ interface TerraformParamsWithVariables extends TerraformParamsWithWorkspace {
}

/**
* Validates the stack at the given root.
*
* Note that this does not set the workspace, so it must be set ahead of calling the function.
* Initialize Terraform.
*/
export async function tfValidate(params: TerraformParams) {
export async function initTerraform(params: TerraformParams) {
const { log, ctx, provider, root } = params

// The Terraform init command is idempotent but can be slow so we first check if the stack is valid
// and return early if it is (if Terraform hasn't been initialized then validate returns false)
const args = ["validate", "-json"]
const res = await terraform(ctx, provider).json({
log,
Expand Down Expand Up @@ -94,8 +94,8 @@ export async function tfValidate(params: TerraformParams) {
)
errorMsg += dedent`\n\n${resultErrors.join("\n")}

Garden tried running "terraform init" but got the following error:\n
${initError.message}`
Garden tried running "terraform init" but got the following error:\n
${initError.message}`
} else {
// "terraform init" went through but there is still a validation error afterwards so we
// add the retry error.
Expand Down Expand Up @@ -143,9 +143,6 @@ type StackStatus = "up-to-date" | "outdated" | "error"
export async function getStackStatus(params: TerraformParamsWithVariables): Promise<StackStatus> {
const { ctx, log, provider, root, variables } = params

await setWorkspace(params)
await tfValidate(params)

const statusLog = log.createLog({ name: "terraform" }).info("Running plan...")

const plan = await terraform(ctx, provider).exec({
Expand Down Expand Up @@ -188,7 +185,7 @@ export async function getStackStatus(params: TerraformParamsWithVariables): Prom
export async function applyStack(params: TerraformParamsWithVariables) {
const { ctx, log, provider, root, variables } = params

await setWorkspace(params)
await ensureWorkspace(params)

const args = ["apply", "-auto-approve", "-input=false", ...(await prepareVariables(root, variables))]
const proc = await terraform(ctx, provider).spawn({ log, args, cwd: root })
Expand Down Expand Up @@ -279,7 +276,7 @@ export async function getWorkspaces(params: TerraformParams) {
* Sets the workspace to use in the Terraform `root`, creating it if it doesn't already exist. Does nothing if
* no `workspace` is set.
*/
export async function setWorkspace(params: TerraformParamsWithWorkspace) {
export async function ensureWorkspace(params: TerraformParamsWithWorkspace) {
const { ctx, provider, root, log, workspace } = params

if (!workspace) {
Expand Down
68 changes: 54 additions & 14 deletions plugins/terraform/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@
*/

import type { TerraformProvider } from "./provider.js"
import { applyStack, getRoot, getStackStatus, getTfOutputs, prepareVariables, setWorkspace } from "./helpers.js"
import {
applyStack,
getRoot,
getStackStatus,
getTfOutputs,
prepareVariables,
ensureWorkspace,
initTerraform,
} from "./helpers.js"
import { deline } from "@garden-io/sdk/build/src/util/string.js"
import type { ProviderHandlers } from "@garden-io/sdk/build/src/types.js"
import { terraform } from "./cli.js"
import { styles } from "@garden-io/core/build/src/logger/styles.js"

// TODO: 0.14, remove this function
export const getEnvironmentStatus: ProviderHandlers["getEnvironmentStatus"] = async ({ ctx, log }) => {
const provider = ctx.provider as TerraformProvider

Expand All @@ -26,6 +35,21 @@ export const getEnvironmentStatus: ProviderHandlers["getEnvironmentStatus"] = as
const variables = provider.config.variables
const workspace = provider.config.workspace || null

// NOTE: This has a side effect although it shouldn't but this handler will be removed
// altogether in 0.14.
await ensureWorkspace({ log, ctx, provider, root, workspace })

const isValidRes = await terraform(ctx, provider).json({
log,
args: ["validate", "-json"],
ignoreError: true,
cwd: root,
})

if (isValidRes.valid !== true) {
return { ready: false, outputs: {} }
}

const status = await getStackStatus({ log, ctx, provider, root, variables, workspace })

if (status === "up-to-date") {
Expand All @@ -50,27 +74,43 @@ export const getEnvironmentStatus: ProviderHandlers["getEnvironmentStatus"] = as

export const prepareEnvironment: ProviderHandlers["prepareEnvironment"] = async ({ ctx, log }) => {
const provider = ctx.provider as TerraformProvider
const isPluginCommand = ctx.command?.name === "plugins" && ctx.command?.args.plugin === provider.name

if (!provider.config.initRoot) {
// Nothing to do!
// Return if there is no root stack, or if we're running one of the terraform plugin commands
if (!provider.config.initRoot || isPluginCommand) {
return { status: { ready: true, outputs: {} } }
}

const envStatus = await getEnvironmentStatus({ ctx, log })
if (envStatus.ready) {
return {
status: envStatus,
}
}

const root = getRoot(ctx, provider)
const workspace = provider.config.workspace || null

// Don't run apply when running plugin commands
if (provider.config.autoApply && !(ctx.command?.name === "plugins" && ctx.command?.args.plugin === provider.name)) {
await applyStack({ ctx, log, provider, root, variables: provider.config.variables, workspace })
await ensureWorkspace({ log, ctx, provider, root, workspace })
await initTerraform({ log, ctx, provider, root })

const status = await getStackStatus({
log,
ctx,
provider,
root,
workspace,
variables: provider.config.variables,
})

if (status === "up-to-date") {
const tfOutputs = await getTfOutputs({ log, ctx, provider, root })
return { status: { ready: true, outputs: tfOutputs } }
} else if (!provider.config.autoApply) {
const tfOutputs = await getTfOutputs({ log, ctx, provider, root })
log.warn(deline`
Terraform stack is not up-to-date and ${styles.underline("autoApply")} is not enabled. Please run
${styles.accent.bold("garden plugins terraform apply-root")} to make sure the stack is in the intended state.
`)
// Make sure the status is not cached when the stack is not up-to-date
return { status: { ready: true, outputs: tfOutputs, disableCache: true } }
}

// Don't run apply when running plugin commands
await applyStack({ ctx, log, provider, root, variables: provider.config.variables, workspace })
const outputs = await getTfOutputs({ log, ctx, provider, root })

return {
Expand Down Expand Up @@ -98,7 +138,7 @@ export const cleanupEnvironment: ProviderHandlers["cleanupEnvironment"] = async
const variables = provider.config.variables
const workspace = provider.config.workspace || null

await setWorkspace({ ctx, provider, root, log, workspace })
await ensureWorkspace({ ctx, provider, root, log, workspace })

const args = ["destroy", "-auto-approve", "-input=false", ...(await prepareVariables(root, variables))]
await terraform(ctx, provider).exec({ log, args, cwd: root })
Expand Down
10 changes: 5 additions & 5 deletions plugins/terraform/test/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { TerraformProvider } from "../src/provider.js"
import type { TestGarden } from "@garden-io/sdk/build/src/testing.js"
import { makeTestGarden } from "@garden-io/sdk/build/src/testing.js"
import type { Log, PluginContext } from "@garden-io/sdk/build/src/types.js"
import { getWorkspaces, setWorkspace } from "../src/helpers.js"
import { getWorkspaces, ensureWorkspace } from "../src/helpers.js"
import { expect } from "chai"
import { defaultTerraformVersion, terraform } from "../src/cli.js"
import { fileURLToPath } from "node:url"
Expand Down Expand Up @@ -94,15 +94,15 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
await terraform(ctx, provider).exec({ args: ["init"], cwd: root, log })
await terraform(ctx, provider).exec({ args: ["workspace", "new", "foo"], cwd: root, log })

await setWorkspace({ ctx, provider, log, root, workspace: null })
await ensureWorkspace({ ctx, provider, log, root, workspace: null })

const { workspaces, selected } = await getWorkspaces({ ctx, provider, log, root })
expect(selected).to.equal("foo")
expect(workspaces).to.eql(["default", "foo"])
})

it("does nothing if already on requested workspace", async () => {
await setWorkspace({ ctx, provider, log, root, workspace: "default" })
await ensureWorkspace({ ctx, provider, log, root, workspace: "default" })

const { workspaces, selected } = await getWorkspaces({ ctx, provider, log, root })
expect(selected).to.equal("default")
Expand All @@ -114,15 +114,15 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
await terraform(ctx, provider).exec({ args: ["workspace", "new", "foo"], cwd: root, log })
await terraform(ctx, provider).exec({ args: ["workspace", "select", "default"], cwd: root, log })

await setWorkspace({ ctx, provider, log, root, workspace: "foo" })
await ensureWorkspace({ ctx, provider, log, root, workspace: "foo" })

const { workspaces, selected } = await getWorkspaces({ ctx, provider, log, root })
expect(selected).to.equal("foo")
expect(workspaces).to.eql(["default", "foo"])
})

it("creates a new workspace if it doesn't already exist", async () => {
await setWorkspace({ ctx, provider, log, root, workspace: "foo" })
await ensureWorkspace({ ctx, provider, log, root, workspace: "foo" })

const { workspaces, selected } = await getWorkspaces({ ctx, provider, log, root })
expect(selected).to.equal("foo")
Expand Down
18 changes: 9 additions & 9 deletions plugins/terraform/test/terraform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { LogLevel } from "@garden-io/sdk/build/src/types.js"
import { gardenPlugin } from "../src/index.js"
import type { TerraformProvider } from "../src/provider.js"
import { DeployTask } from "@garden-io/core/build/src/tasks/deploy.js"
import { getWorkspaces, setWorkspace } from "../src/helpers.js"
import { getWorkspaces, ensureWorkspace } from "../src/helpers.js"
import { resolveAction } from "@garden-io/core/build/src/graph/actions.js"
import { RunTask } from "@garden-io/core/build/src/tasks/run.js"
import { defaultTerraformVersion } from "../src/cli.js"
Expand Down Expand Up @@ -433,7 +433,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
const provider = (await _garden.resolveProvider({ log: garden.log, name: "terraform" })) as TerraformProvider
const ctx = await _garden.getPluginContext({ provider, templateContext: undefined, events: undefined })

await setWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

const _graph = await _garden.getConfigGraph({ log: _garden.log, emit: false })

Expand Down Expand Up @@ -478,7 +478,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
const provider = (await _garden.resolveProvider({ log: garden.log, name: "terraform" })) as TerraformProvider
const _ctx = await _garden.getPluginContext({ provider, templateContext: undefined, events: undefined })

await setWorkspace({ ctx: _ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx: _ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

const _graph = await _garden.getConfigGraph({ log: _garden.log, emit: false })

Expand Down Expand Up @@ -523,7 +523,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
const provider = (await _garden.resolveProvider({ log: _garden.log, name: "terraform" })) as TerraformProvider
const ctx = await _garden.getPluginContext({ provider, templateContext: undefined, events: undefined })

await setWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

graph = await _garden.getConfigGraph({ log: _garden.log, emit: false })

Expand Down Expand Up @@ -760,7 +760,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
log: _garden.log,
})

await setWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

await actions.deploy.delete({ action: _action, log: _action.createLog(_garden.log), graph: _graph })

Expand Down Expand Up @@ -878,7 +878,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
const provider = (await _garden.resolveProvider({ log: garden.log, name: "terraform" })) as TerraformProvider
const ctx = await _garden.getPluginContext({ provider, templateContext: undefined, events: undefined })

await setWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

const _graph = await _garden.getConfigGraph({ log: _garden.log, emit: false })

Expand Down Expand Up @@ -923,7 +923,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
const provider = (await _garden.resolveProvider({ log: garden.log, name: "terraform" })) as TerraformProvider
const _ctx = await _garden.getPluginContext({ provider, templateContext: undefined, events: undefined })

await setWorkspace({ ctx: _ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx: _ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

const _graph = await _garden.getConfigGraph({ log: _garden.log, emit: false })

Expand Down Expand Up @@ -968,7 +968,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
const provider = (await _garden.resolveProvider({ log: _garden.log, name: "terraform" })) as TerraformProvider
const ctx = await _garden.getPluginContext({ provider, templateContext: undefined, events: undefined })

await setWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

graph = await _garden.getConfigGraph({ log: _garden.log, emit: false })

Expand Down Expand Up @@ -1204,7 +1204,7 @@ for (const terraformVersion of ["0.13.3", defaultTerraformVersion]) {
log: _garden.log,
})

await setWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })
await ensureWorkspace({ ctx, provider, root: tfRoot, log: _garden.log, workspace: "default" })

await actions.deploy.delete({ action: _action, log: _action.createLog(_garden.log), graph: _graph })

Expand Down