Skip to content

Commit

Permalink
refactor(terraform): don't init in status handler (#6825)
Browse files Browse the repository at this point in the history
Before this fix, we were calling `terraform init` in the
`getStatusHandler` which is an undesired side effect.

With this refactor we now call it in the `prepareEnvironment` handler
which is were it should be.

Also note that in PR #6706 we changed the provider init flow such that
we now always call `prepareEnvironment` and the `getStatusHandler`
isn't really used any more.

That's why I also removed the call to that handler from the
`prepareEnvironment` function and instead moved the logic there.

It's basically duplicated across both handlers now which is fine because
we're removing the status handler in 0.14.
  • Loading branch information
eysi09 authored Feb 8, 2025
1 parent 0bda6c4 commit 3c07295
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 47 deletions.
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

0 comments on commit 3c07295

Please sign in to comment.