Compare commits

...

4 Commits

Author SHA1 Message Date
Collin J. Doering 913c2f0e67
.drone/ci.libsonnet: Add and renamed methods relocated from .drone.jsonnet: guix.pipeline, guix.step, awsDeployStep
* .drone.yml: Update file after updates to .drone.jsonnet

* .drone.jsonnet: Relocate guix_pipeline, guix_step, and deployStep to .drone/ci.libsonnet
library. Adjust existing function names to match new names in library.

Explicitly pass PLAN environment variable to ci.awsDeployStep for plan and apply stages
2021-12-11 13:19:55 -05:00
Collin J. Doering eaf41637ab
.gitignore: Do not ignore hidden files 2021-12-11 12:53:30 -05:00
Collin J. Doering 284abdf916
.drone.jsonnet: Move ci local to its own library in .drone/ci.libsonnet 2021-12-11 12:53:01 -05:00
Collin J. Doering faad56813b
.drone.jsonnet: Remove setting of root user in /etc/passwd
Adding the root user to /etc/passwd was only required when attempting to run guix shell
`-C|--container' commands, however guix shell containers will not work without privileged
docker, so when running via ci, we depend on the provided minimal guix container (build with
guix via 'guix pack -f docker ...').
2021-12-10 22:32:42 -05:00
4 changed files with 206 additions and 210 deletions

View File

@ -1,214 +1,21 @@
local droneStatus = ['success', 'failure'];
local ci = {
pipeline:: {
new()::
self.withKind("pipeline"),
withName(name)::
self + { name: name },
withKind(kind)::
self + { kind: kind },
withType(type)::
self + { type: type },
withNode(node)::
self + { node: node },
withTrigger(trigger)::
self + { trigger: trigger },
withDependsOn(n)::
self + { depends_on: n },
withNodeSelector(ns)::
self + { node_selector: ns },
withSteps(steps)::
self + if std.type(steps) == 'array'
then { steps: steps }
else { steps: [steps] },
step:: {
new(name='', image='')::
self.withName(name).withImage(image).withPullIfNotExists(),
withName(name)::
self + { name: name },
withImage(image)::
self + if image != '' then { image: image } else {},
withAlwaysPull()::
self + { pull: 'always' },
withPullIfNotExists()::
self + { pull: 'if-not-exists' },
withCommands(commands)::
self + if std.type(commands) == 'array'
then { commands: commands }
else { commands: [commands] },
withTrigger(trigger)::
self + { trigger: trigger }, // TODO: this is duplicated in pipeline object
withEnv(envs)::
self + { environment+: envs },
withRuntimeEnvVar(envs)::
local existingCmds = if std.objectHas(self, "commands") then self.commands else [];
self + {
commands: std.map(function (i) std.format('export %s="%s"', [i, envs[i]]),
std.objectFields(envs)) + existingCmds
},
withWhen(when)::
self + { when: when },
withSettings(settings)::
self + { settings: settings },
},
when:: {
new()::
self + {},
withBranch(branch)::
self + if std.type(branch) == 'array'
then { branch: branch }
else { branch: [branch] },
withEvent(e)::
self + if std.type(e) == 'array'
then { event: e }
else { event: [e] },
withStatus(s)::
self + if std.type(s) == 'array'
then { status: s }
else { status: [s] },
withStatusAll()::
self.withStatus(droneStatus),
},
},
trigger:: {
new()::
self + {},
withBranch(branch)::
self + if std.type(branch) == 'array'
then { branch: branch }
else { branch: [branch] },
withEvent(e)::
self + if std.type(e) == 'array'
then { event: e }
else { event: [e] },
withStatus(s)::
self + if std.type(s) == 'array'
then { status: s }
else { status: [s] },
withStatusAll()::
self.withStatus(droneStatus),
},
env_from_secret(dict):: {
[key]: {
from_secret: dict[key],
}
for key in std.objectFields(dict)
},
promoteStep(env,
secret_name_drone_token="drone_token",
image="docker.nexus.home.rekahsoft.ca/drone/cli:1.4-alpine")::
local dronePromoteCmd(env) = [
"export DRONE_SERVER=\"${DRONE_SYSTEM_PROTO}://${DRONE_SYSTEM_HOST}\"",
"export DRONE_TOKEN",
std.format('DRONE_PROMOTED_PIPELINE_ID=$(drone build promote --format \'{{ .Number }}\' "$DRONE_REPO" "$DRONE_BUILD_NUMBER" "%s")', env),
'while status="$(drone build info --format \'{{ .Status }}\' $DRONE_REPO $DRONE_PROMOTED_PIPELINE_ID)"; do
case "$status" in
pending|running)
sleep 30s
;;
success)
break
;;
failure|error|killed)
echo "Promoted job with id $DRONE_PROMOTED_PIPELINE_ID failed with status \'$status\'."
exit 1
;;
*)
echo "Unknown pipeline status \'$status\'."
exit 1
esac
done',
];
ci.pipeline.step.new(std.format("promote-%s", env), image)
.withWhen(ci.pipeline.when.new()
.withBranch("master")
.withEvent("push"))
.withCommands(dronePromoteCmd(env))
.withEnv(ci.env_from_secret({
DRONE_TOKEN: "drone_token"
}))
};
local guix_pipeline(name) = ci.pipeline.new()
.withName(name)
.withType("docker")
.withNode({ "guix": "on"});
local guix_step(name,
commands,
image="docker.nexus.home.rekahsoft.ca/guix:latest") =
ci.pipeline.step.new(name, image).withPullIfNotExists().withCommands(commands);
local guix_step_time_machine(name,
commands,
cwd=".",
channels="channels.scm",
image="docker.nexus.home.rekahsoft.ca/guix:latest") =
ci.pipeline.step.new(name, image).withPullIfNotExists().withCommands(
// Conditionally change directory
(if cwd == "."
then [] else [std.format("cd %s", cwd)]) +
// Drone-ci does not populate a /etc/passwd which causes issues with guix
["echo 'root:x:0:0:root:/root:/bin/bash' >> /etc/passwd"] +
// Expand provide guix commands into executable shell
std.map(function(i) std.format("guix time-machine -C %s -- %s", [channels, i]),
if std.type(commands) == 'array' then commands else [commands]));
local deployStep(name, target=name, args=[]) = guix_step_time_machine(
name,
std.format('shell -m manifest.scm -- make %s ENV="${DRONE_DEPLOY_TO}" %s', [target, std.join(" ", args)]),
cwd="infra",
channels="../channels.scm")
.withEnv({ PLAN: "out.plan" } + ci.env_from_secret({
AWS_ACCESS_KEY_ID: "aws_access_key_id",
AWS_SECRET_ACCESS_KEY: "aws_secret_access_key",
}));
local ci = import '.drone/ci.libsonnet';
[
guix_pipeline("validate").withTrigger(ci.trigger.new().withEvent(["push", "pull_request", "tag"])).withSteps([
guix_step_time_machine("build", "build -f guix.scm"),
ci.guix.pipeline("validate").withTrigger(ci.trigger.new().withEvent(["push", "pull_request", "tag"])).withSteps([
ci.guix.stepTimeMachine("build", "build -f guix.scm"),
ci.promoteStep("staging"),
ci.promoteStep("production"),
]),
guix_pipeline("deploy").withTrigger(ci.trigger.new().withEvent("promote")).withSteps([
deployStep("init", "setup"),
deployStep("plan").withRuntimeEnvVar({
ci.guix.pipeline("deploy").withTrigger(ci.trigger.new().withEvent("promote")).withSteps([
ci.awsDeployStep("init", "setup"),
ci.awsDeployStep("plan").withEnv({
PLAN: "out.plan"
}).withRuntimeEnvVar({
TF_VAR_site_static_files_dir: "$(guix time-machine -C channels.scm -- build -f guix.scm | grep -e '^.*-site$')"
}),
deployStep("deploy"),
ci.awsDeployStep("deploy").withEnv({
PLAN: "out.plan"
}),
])
]

View File

@ -12,7 +12,6 @@ steps:
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/guix:latest
commands:
- echo 'root:x:0:0:root:/root:/bin/bash' >> /etc/passwd
- guix time-machine -C channels.scm -- build -f guix.scm
- name: promote-staging
@ -73,14 +72,12 @@ steps:
image: docker.nexus.home.rekahsoft.ca/guix:latest
commands:
- cd infra
- echo 'root:x:0:0:root:/root:/bin/bash' >> /etc/passwd
- "guix time-machine -C ../channels.scm -- shell -m manifest.scm -- make setup ENV=\"${DRONE_DEPLOY_TO}\" "
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
PLAN: out.plan
- name: plan
pull: if-not-exists
@ -88,7 +85,6 @@ steps:
commands:
- export TF_VAR_site_static_files_dir="$(guix time-machine -C channels.scm -- build -f guix.scm | grep -e '^.*-site$')"
- cd infra
- echo 'root:x:0:0:root:/root:/bin/bash' >> /etc/passwd
- "guix time-machine -C ../channels.scm -- shell -m manifest.scm -- make plan ENV=\"${DRONE_DEPLOY_TO}\" "
environment:
AWS_ACCESS_KEY_ID:
@ -102,7 +98,6 @@ steps:
image: docker.nexus.home.rekahsoft.ca/guix:latest
commands:
- cd infra
- echo 'root:x:0:0:root:/root:/bin/bash' >> /etc/passwd
- "guix time-machine -C ../channels.scm -- shell -m manifest.scm -- make deploy ENV=\"${DRONE_DEPLOY_TO}\" "
environment:
AWS_ACCESS_KEY_ID:

195
.drone/ci.libsonnet Normal file
View File

@ -0,0 +1,195 @@
{
local ci = self,
local droneStatus = ['success', 'failure'],
pipeline:: {
new()::
self.withKind("pipeline"),
withName(name)::
self + { name: name },
withKind(kind)::
self + { kind: kind },
withType(type)::
self + { type: type },
withNode(node)::
self + { node: node },
withTrigger(trigger)::
self + { trigger: trigger },
withDependsOn(n)::
self + { depends_on: n },
withNodeSelector(ns)::
self + { node_selector: ns },
withSteps(steps)::
self + if std.type(steps) == 'array'
then { steps: steps }
else { steps: [steps] },
step:: {
new(name='', image='')::
self.withName(name).withImage(image).withPullIfNotExists(),
withName(name)::
self + { name: name },
withImage(image)::
self + if image != '' then { image: image } else {},
withAlwaysPull()::
self + { pull: 'always' },
withPullIfNotExists()::
self + { pull: 'if-not-exists' },
withCommands(commands)::
self + if std.type(commands) == 'array'
then { commands: commands }
else { commands: [commands] },
withTrigger(trigger)::
self + { trigger: trigger }, // TODO: this is duplicated in pipeline object
withEnv(envs)::
self + { environment+: envs },
withRuntimeEnvVar(envs)::
local existingCmds = if std.objectHas(self, "commands") then self.commands else [];
self + {
commands: std.map(function (i) std.format('export %s="%s"', [i, envs[i]]),
std.objectFields(envs)) + existingCmds
},
withWhen(when)::
self + { when: when },
withSettings(settings)::
self + { settings: settings },
},
when:: {
new()::
self + {},
withBranch(branch)::
self + if std.type(branch) == 'array'
then { branch: branch }
else { branch: [branch] },
withEvent(e)::
self + if std.type(e) == 'array'
then { event: e }
else { event: [e] },
withStatus(s)::
self + if std.type(s) == 'array'
then { status: s }
else { status: [s] },
withStatusAll()::
self.withStatus(droneStatus),
},
},
trigger:: {
new()::
self + {},
withBranch(branch)::
self + if std.type(branch) == 'array'
then { branch: branch }
else { branch: [branch] },
withEvent(e)::
self + if std.type(e) == 'array'
then { event: e }
else { event: [e] },
withStatus(s)::
self + if std.type(s) == 'array'
then { status: s }
else { status: [s] },
withStatusAll()::
self.withStatus(droneStatus),
},
env_from_secret(dict):: {
[key]: {
from_secret: dict[key],
}
for key in std.objectFields(dict)
},
guix:: {
pipeline(name)::
ci.pipeline.new()
.withName(name)
.withType("docker")
.withNode({ "guix": "on"}),
step(name, commands, image="docker.nexus.home.rekahsoft.ca/guix:latest")::
ci.pipeline.step.new(name, image).withPullIfNotExists().withCommands(commands),
stepTimeMachine(name, commands, cwd=".", channels="channels.scm", image="docker.nexus.home.rekahsoft.ca/guix:latest")::
ci.pipeline.step.new(name, image).withPullIfNotExists().withCommands(
// Conditionally change directory
(if cwd == "."
then [] else [std.format("cd %s", cwd)]) +
// Expand provide guix commands into executable shell
std.map(function(i) std.format("guix time-machine -C %s -- %s", [channels, i]),
if std.type(commands) == 'array' then commands else [commands])),
},
promoteStep(env,
secret_name_drone_token="drone_token",
image="docker.nexus.home.rekahsoft.ca/drone/cli:1.4-alpine")::
local dronePromoteCmd(env) = [
"export DRONE_SERVER=\"${DRONE_SYSTEM_PROTO}://${DRONE_SYSTEM_HOST}\"",
"export DRONE_TOKEN",
std.format('DRONE_PROMOTED_PIPELINE_ID=$(drone build promote --format \'{{ .Number }}\' "$DRONE_REPO" "$DRONE_BUILD_NUMBER" "%s")', env),
'while status="$(drone build info --format \'{{ .Status }}\' $DRONE_REPO $DRONE_PROMOTED_PIPELINE_ID)"; do
case "$status" in
pending|running)
sleep 30s
;;
success)
break
;;
failure|error|killed)
echo "Promoted job with id $DRONE_PROMOTED_PIPELINE_ID failed with status \'$status\'."
exit 1
;;
*)
echo "Unknown pipeline status \'$status\'."
exit 1
esac
done',
];
ci.pipeline.step.new(std.format("promote-%s", env), image)
.withWhen(ci.pipeline.when.new()
.withBranch("master")
.withEvent("push"))
.withCommands(dronePromoteCmd(env))
.withEnv(ci.env_from_secret({
DRONE_TOKEN: "drone_token"
})),
awsDeployStep(name, target=name, args=[])::
ci.guix.stepTimeMachine(
name,
std.format('shell -m manifest.scm -- make %s ENV="${DRONE_DEPLOY_TO}" %s', [target, std.join(" ", args)]),
cwd="infra",
channels="../channels.scm")
.withEnv(ci.env_from_secret({
AWS_ACCESS_KEY_ID: "aws_access_key_id",
AWS_SECRET_ACCESS_KEY: "aws_secret_access_key",
})),
}

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
# Editor specific files
.*
*~
.dir-locals.el
.tern-port