Skip to content

Commit

Permalink
perf: generic plugin now keeps track of last built version
Browse files Browse the repository at this point in the history
  • Loading branch information
edvald committed Jul 10, 2018
1 parent 1c6d492 commit ab3714b
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 27 deletions.
29 changes: 24 additions & 5 deletions src/plugins/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { exec } from "child-process-promise"
import * as Joi from "joi"
import { join } from "path"
import {
joiArray,
joiEnvVars,
Expand Down Expand Up @@ -42,8 +43,10 @@ import {
baseTestSpecSchema,
} from "../types/test"
import { spawn } from "../util/util"
import { TreeVersion, writeVersionFile, readVersionFile } from "../vcs/base"

export const name = "generic"
export const buildVersionFilename = ".garden-build-version"

export interface GenericTestSpec extends BaseTestSpec {
command: string[],
Expand Down Expand Up @@ -92,8 +95,6 @@ export async function parseGenericModule(
}

export async function buildGenericModule({ module }: BuildModuleParams<GenericModule>): Promise<BuildResult> {
// By default we run the specified build command in the module root, if any.
// TODO: Keep track of which version has been built (needs local data store/cache).
const config: ModuleConfig = module.config

if (config.build.command) {
Expand All @@ -103,6 +104,14 @@ export async function buildGenericModule({ module }: BuildModuleParams<GenericMo
env: { ...process.env, ...module.spec.env },
})

// keep track of which version has been built
const buildVersionFilePath = join(buildPath, buildVersionFilename)
const version = await module.getVersion()
await writeVersionFile(buildVersionFilePath, {
latestCommit: version.versionString,
dirtyTimestamp: version.dirtyTimestamp,
})

return {
fresh: true,
buildLog: result.stdout,
Expand Down Expand Up @@ -146,9 +155,19 @@ export const genericPlugin: GardenPlugin = {
testModule: testGenericModule,

async getModuleBuildStatus({ module }: GetModuleBuildStatusParams): Promise<BuildStatus> {
// Each module handler should keep track of this for now.
// Defaults to return false if a build command is specified.
return { ready: !module.config.build.command }
if (!module.config.build.command) {
return { ready: true }
}

const buildVersionFilePath = join(await module.getBuildPath(), buildVersionFilename)
const builtVersion = await readVersionFile(buildVersionFilePath)
const moduleVersion = await module.getVersion()

if (builtVersion && builtVersion.latestCommit === moduleVersion.versionString) {
return { ready: true }
}

return { ready: false }
},
},
},
Expand Down
2 changes: 0 additions & 2 deletions src/types/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,3 @@ export class Module<
}
}
}

export type ModuleConfigType<M extends Module> = M["_ConfigType"]
53 changes: 33 additions & 20 deletions src/vcs/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as Joi from "joi"
import { validate } from "../types/common"
import { join } from "path"
import { GARDEN_VERSIONFILE_NAME } from "../constants"
import { pathExists, readFile } from "fs-extra"
import { pathExists, readFile, writeFile } from "fs-extra"
import { ConfigurationError } from "../exceptions"

export const NEW_MODULE_VERSION = "0000000000"
Expand Down Expand Up @@ -74,25 +74,8 @@ export abstract class VcsHandler {
async resolveTreeVersion(module: Module): Promise<TreeVersion> {
// the version file is used internally to specify versions outside of source control
const versionFilePath = join(module.path, GARDEN_VERSIONFILE_NAME)
const versionFileContents = await pathExists(versionFilePath)
&& (await readFile(versionFilePath)).toString().trim()

if (!!versionFileContents) {
try {
return validate(JSON.parse(versionFileContents), treeVersionSchema)
} catch (err) {
throw new ConfigurationError(
`Unable to parse ${GARDEN_VERSIONFILE_NAME} as valid version file in module directory ${module.path}`,
{
modulePath: module.path,
versionFilePath,
versionFileContents,
},
)
}
} else {
return this.getTreeVersion([module.path])
}
const fileVersion = await readVersionFile(versionFilePath)
return fileVersion || this.getTreeVersion([module.path])
}

async resolveVersion(module: Module, dependencies: Module[]): Promise<ModuleVersion> {
Expand Down Expand Up @@ -170,3 +153,33 @@ function hashVersions(versions: NamedTreeVersion[]) {
// this format is kinda arbitrary, but prefixing the "v" is useful to visually spot hashed versions
return "v" + versionHash.digest("hex").slice(0, 10)
}

export async function readVersionFile(path: string): Promise<TreeVersion | null> {
if (!(await pathExists(path))) {
return null
}

// this is used internally to specify version outside of source control
const versionFileContents = (await readFile(path)).toString().trim()

if (!versionFileContents) {
return null
}

try {
return validate(JSON.parse(versionFileContents), treeVersionSchema)
} catch (error) {
throw new ConfigurationError(
`Unable to parse ${path} as valid version file`,
{
path,
versionFileContents,
error,
},
)
}
}

export async function writeVersionFile(path: string, version: TreeVersion) {
await writeFile(path, JSON.stringify(version))
}
3 changes: 3 additions & 0 deletions test/src/commands/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ testProviderNoPush.pluginName = "test-plugin"

async function getTestContext() {
const garden = await Garden.factory(projectRootB, { plugins: [testProvider] })
await garden.clearBuilds()
return garden.pluginContext
}

describe("PushCommand", () => {
// TODO: Verify that services don't get redeployed when same version is already deployed.

it("should build and push modules in a project", async () => {
const ctx = await getTestContext()
const command = new PushCommand()
Expand Down Expand Up @@ -171,6 +173,7 @@ describe("PushCommand", () => {

it("should fail gracefully if module does not have a provider for push", async () => {
const ctx = await makeTestContextA()
await ctx.clearBuilds()

const command = new PushCommand()

Expand Down
74 changes: 74 additions & 0 deletions test/src/plugins/generic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { expect } from "chai"
import {
join,
resolve,
} from "path"
import { Garden } from "../../../src/garden"
import { PluginContext } from "../../../src/plugin-context"
import {
gardenPlugin,
} from "../../../src/plugins/container"
import {
buildVersionFilename,
} from "../../../src/plugins/generic"
import { Environment } from "../../../src/types/common"
import {
readVersionFile,
writeVersionFile,
} from "../../../src/vcs/base"
import {
dataDir,
makeTestGarden,
} from "../../helpers"

describe("generic plugin", () => {
const projectRoot = resolve(dataDir, "test-project-a")
const moduleName = "module-a"

let garden: Garden
let ctx: PluginContext
let env: Environment

beforeEach(async () => {
garden = await makeTestGarden(projectRoot, [gardenPlugin])
ctx = garden.pluginContext
env = garden.getEnvironment()
await garden.clearBuilds()
})

describe("getModuleBuildStatus", () => {
it("should read a build version file if it exists", async () => {
const module = await ctx.getModule(moduleName)
const version = await module.getVersion()
const buildPath = await module.getBuildPath()
const versionFilePath = join(buildPath, buildVersionFilename)

await writeVersionFile(versionFilePath, {
latestCommit: version.versionString,
dirtyTimestamp: version.dirtyTimestamp,
})

const result = await ctx.getModuleBuildStatus({ moduleName })

expect(result.ready).to.be.true
})
})

describe("buildModule", () => {
it("should write a build version file after building", async () => {
const module = await ctx.getModule(moduleName)
const version = await module.getVersion()
const buildPath = await module.getBuildPath()
const versionFilePath = join(buildPath, buildVersionFilename)

await ctx.buildModule({ moduleName })

const versionFileContents = await readVersionFile(versionFilePath)

expect(versionFileContents).to.eql({
latestCommit: version.versionString,
dirtyTimestamp: version.dirtyTimestamp,
})
})
})
})

0 comments on commit ab3714b

Please sign in to comment.