Skip to content

Commit

Permalink
feat(core): provide git branch to template strings
Browse files Browse the repository at this point in the history
The current Git branch can now be used in template strings under
`${git.branch}`.
  • Loading branch information
thsig committed Nov 25, 2020
1 parent bf854a5 commit 5d79d97
Show file tree
Hide file tree
Showing 12 changed files with 505 additions and 64 deletions.
65 changes: 42 additions & 23 deletions core/src/config/config-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,33 @@ class ProjectContext extends ConfigContext {
}
}

class GitContext extends ConfigContext {
@schema(
joi
.string()
.description(
dedent`
The current Git branch, if available. Resolves to an empty string if HEAD is in a detached state
(e.g. when rebasing), or if the repository has no commits.
When using remote sources, the branch used is that of the project/top-level repository (the one that contains
the project configuration).
The branch is computed at the start of the Garden command's execution, and is not updated if the current
branch changes during the command's execution (which could happen, for example, when using watch-mode
commands).
`
)
.example("my-feature-branch")
)
public branch: string

constructor(root: ConfigContext, branch: string) {
super(root)
this.branch = branch
}
}

/**
* This context is available for template strings in the `defaultEnvironment` field in project configs.
*/
Expand All @@ -310,24 +337,33 @@ export class DefaultEnvironmentContext extends ConfigContext {
@schema(ProjectContext.getSchema().description("Information about the Garden project."))
public project: ProjectContext

@schema(
GitContext.getSchema().description("Information about the current state of the project's local git repository.")
)
public git: GitContext

constructor({
projectName,
artifactsPath,
branch,
username,
}: {
projectName: string
artifactsPath: string
branch: string
username?: string
}) {
super()
this.local = new LocalContext(this, artifactsPath, username)
this.git = new GitContext(this, branch)
this.project = new ProjectContext(this, projectName)
}
}

export interface ProjectConfigContextParams {
projectName: string
artifactsPath: string
branch: string
username?: string
secrets: PrimitiveMap
}
Expand All @@ -340,16 +376,6 @@ export interface ProjectConfigContextParams {
* `secrets`.
*/
export class ProjectConfigContext extends DefaultEnvironmentContext {
@schema(
LocalContext.getSchema().description(
"Context variables that are specific to the currently running environment/machine."
)
)
public local: LocalContext

@schema(ProjectContext.getSchema().description("Information about the Garden project."))
public project: ProjectContext

@schema(
joiStringMap(joi.string().description("The secret's value."))
.description("A map of all secrets for this project in the current environment.")
Expand All @@ -360,8 +386,8 @@ export class ProjectConfigContext extends DefaultEnvironmentContext {
)
public secrets: PrimitiveMap

constructor({ projectName, artifactsPath, username, secrets }: ProjectConfigContextParams) {
super({ projectName, artifactsPath, username })
constructor({ projectName, artifactsPath, branch, username, secrets }: ProjectConfigContextParams) {
super({ projectName, artifactsPath, branch, username })
this.secrets = secrets
}
}
Expand All @@ -370,16 +396,6 @@ export class ProjectConfigContext extends DefaultEnvironmentContext {
* This context is available for template strings for all `environments[]` fields (except name)
*/
export class EnvironmentConfigContext extends ProjectConfigContext {
@schema(
LocalContext.getSchema().description(
"Context variables that are specific to the currently running environment/machine."
)
)
public local: LocalContext

@schema(ProjectContext.getSchema().description("Information about the Garden project."))
public project: ProjectContext

@schema(
joiVariables()
.description("A map of all variables defined in the project configuration.")
Expand All @@ -403,17 +419,19 @@ export class EnvironmentConfigContext extends ProjectConfigContext {
constructor({
projectName,
artifactsPath,
branch,
username,
variables,
secrets,
}: {
projectName: string
artifactsPath: string
branch: string
username?: string
variables: DeepPrimitiveMap
secrets: PrimitiveMap
}) {
super({ projectName, artifactsPath, username, secrets })
super({ projectName, artifactsPath, branch, username, secrets })
this.variables = this.var = variables
}
}
Expand Down Expand Up @@ -478,6 +496,7 @@ export class WorkflowConfigContext extends EnvironmentConfigContext {
super({
projectName: garden.projectName,
artifactsPath: garden.artifactsPath,
branch: garden.vcsBranch,
username: garden.username,
variables: garden.variables,
secrets: garden.secrets,
Expand Down
5 changes: 3 additions & 2 deletions core/src/config/module-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export async function resolveModuleTemplate(
...resource,
modules: [],
}
const context = new ProjectConfigContext(garden)
const context = new ProjectConfigContext({ ...garden, branch: garden.vcsBranch })
const resolved = resolveTemplateStrings(partial, context)

// Validate the partial config
Expand Down Expand Up @@ -109,7 +109,7 @@ export async function resolveTemplatedModule(
templates: { [name: string]: ModuleTemplateConfig }
) {
// Resolve template strings for fields
const resolved = resolveTemplateStrings(config, new ProjectConfigContext(garden))
const resolved = resolveTemplateStrings(config, new ProjectConfigContext({ ...garden, branch: garden.vcsBranch }))
const configType = "templated module " + resolved.name

let resolvedSpec = omit(resolved.spec, "build")
Expand Down Expand Up @@ -155,6 +155,7 @@ export async function resolveTemplatedModule(
// Prepare modules and resolve templated names
const context = new ModuleTemplateConfigContext({
...garden,
branch: garden.vcsBranch,
parentName: resolved.name,
templateName: template.name,
inputs,
Expand Down
8 changes: 6 additions & 2 deletions core/src/config/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,14 @@ export function resolveProjectConfig({
defaultEnvironment,
config,
artifactsPath,
branch,
username,
secrets,
}: {
defaultEnvironment: string
config: ProjectConfig
artifactsPath: string
branch: string
username: string
secrets: PrimitiveMap
}): ProjectConfig {
Expand All @@ -419,7 +421,7 @@ export function resolveProjectConfig({
variables: config.variables,
environments: [],
},
new ProjectConfigContext({ projectName: name, artifactsPath, username, secrets })
new ProjectConfigContext({ projectName: name, artifactsPath, branch, username, secrets })
)

// Validate after resolving global fields
Expand Down Expand Up @@ -495,12 +497,14 @@ export async function pickEnvironment({
projectConfig,
envString,
artifactsPath,
branch,
username,
secrets,
}: {
projectConfig: ProjectConfig
envString: string
artifactsPath: string
branch: string
username: string
secrets: PrimitiveMap
}) {
Expand All @@ -527,7 +531,7 @@ export async function pickEnvironment({
// Resolve template strings in the environment config, except providers
environmentConfig = resolveTemplateStrings(
{ ...environmentConfig, providers: [] },
new EnvironmentConfigContext({ projectName, artifactsPath, username, variables: projectVariables, secrets })
new EnvironmentConfigContext({ projectName, artifactsPath, branch, username, variables: projectVariables, secrets })
)

environmentConfig = validateWithPath({
Expand Down
18 changes: 12 additions & 6 deletions core/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export interface GardenOpts {

export interface GardenParams {
artifactsPath: string
vcsBranch: string
buildDir: BuildDir
clientAuthToken: string | null
enterpriseDomain: string | null
Expand Down Expand Up @@ -193,6 +194,7 @@ export class Garden {
public readonly buildDir: BuildDir
public readonly gardenDirPath: string
public readonly artifactsPath: string
public readonly vcsBranch: string
public readonly opts: GardenOpts
private readonly providerConfigs: GenericProviderConfig[]
public readonly workingCopyId: string
Expand All @@ -218,6 +220,7 @@ export class Garden {
this.gardenDirPath = params.gardenDirPath
this.log = params.log
this.artifactsPath = params.artifactsPath
this.vcsBranch = params.vcsBranch
this.opts = params.opts
this.rawOutputs = params.outputs
this.production = params.production
Expand Down Expand Up @@ -308,9 +311,13 @@ export class Garden {
// Connect to the state storage
await ensureConnected()

const { sources: projectSources, path: projectRoot } = config

const vcsBranch = (await new GitHandler(projectRoot, gardenDirPath, []).getBranchName(log, projectRoot)) || ""

const defaultEnvironmentName = resolveTemplateString(
config.defaultEnvironment,
new DefaultEnvironmentContext({ projectName, artifactsPath, username: _username })
new DefaultEnvironmentContext({ projectName, artifactsPath, branch: vcsBranch, username: _username })
) as string

const defaultEnvironment = getDefaultEnvironmentName(defaultEnvironmentName, config)
Expand All @@ -336,16 +343,18 @@ export class Garden {
defaultEnvironment: defaultEnvironmentName,
config,
artifactsPath,
branch: vcsBranch,
username: _username,
secrets,
})

const { sources: projectSources, path: projectRoot } = config
const vcs = new GitHandler(projectRoot, gardenDirPath, config.dotIgnoreFiles)

let { namespace, providers, variables, production } = await pickEnvironment({
projectConfig: config,
envString: environmentStr,
artifactsPath,
branch: vcsBranch,
username: _username,
secrets,
})
Expand All @@ -364,12 +373,9 @@ export class Garden {
...fixedProjectExcludes,
]

// Ensure the project root is in a git repo
const vcs = new GitHandler(projectRoot, gardenDirPath, config.dotIgnoreFiles)
await vcs.getRepoRoot(log, projectRoot)

const garden = new this({
artifactsPath,
vcsBranch,
sessionId,
clientAuthToken,
enterpriseDomain,
Expand Down
36 changes: 27 additions & 9 deletions core/src/vcs/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export class GitHandler extends VcsHandler {
if (this.repoRoots.has(path)) {
return this.repoRoots.get(path)
}

const git = this.gitCli(log, path)

try {
Expand All @@ -95,14 +94,7 @@ export class GitHandler extends VcsHandler {
} catch (err) {
if (err.exitCode === 128) {
// Throw nice error when we detect that we're not in a repo root
throw new RuntimeError(
deline`
Path ${path} is not in a git repository root. Garden must be run from within a git repo.
Please run \`git init\` if you're starting a new project and repository, or move the project to an
existing repository, and try again.
`,
{ path }
)
throw new RuntimeError(notInRepoRootErrorMessage(path), { path })
} else {
throw err
}
Expand Down Expand Up @@ -417,4 +409,30 @@ export class GitHandler extends VcsHandler {
}
return undefined
}

async getBranchName(log: LogEntry, path: string): Promise<string | undefined> {
const git = this.gitCli(log, path)
try {
return (await git("rev-parse", "--abbrev-ref", "HEAD"))[0]
} catch (err) {
if (err.exitCode === 128) {
try {
// If this doesn't throw, then we're in a repo with no commits, or with a detached HEAD.
await git("rev-parse", "--show-toplevel")
return undefined
} catch (notInRepoError) {
// Throw nice error when we detect that we're not in a repo root
throw new RuntimeError(notInRepoRootErrorMessage(path), { path })
}
} else {
throw err
}
}
}
}

const notInRepoRootErrorMessage = (path: string) => deline`
Path ${path} is not in a git repository root. Garden must be run from within a git repo.
Please run \`git init\` if you're starting a new project and repository, or move the project to an
existing repository, and try again.
`
1 change: 1 addition & 0 deletions core/src/vcs/vcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export abstract class VcsHandler {
abstract async ensureRemoteSource(params: RemoteSourceParams): Promise<string>
abstract async updateRemoteSource(params: RemoteSourceParams): Promise<void>
abstract async getOriginName(log: LogEntry): Promise<string | undefined>
abstract async getBranchName(log: LogEntry, path: string): Promise<string | undefined>

async getTreeVersion(log: LogEntry, projectName: string, moduleConfig: ModuleConfig): Promise<TreeVersion> {
const configPath = moduleConfig.configPath
Expand Down
Loading

0 comments on commit 5d79d97

Please sign in to comment.