From 96991585cd4369f6514e434177a5159413c64a27 Mon Sep 17 00:00:00 2001 From: Marco Streich Date: Fri, 22 Jan 2021 10:35:36 +0100 Subject: [PATCH] - Add socket activation to systemd unit which triggers nixos-rebuild - Add call to socket to deployment workflow - Improve queued deploymentspec error messages and other minor stuff - Add CI --- .gitlab-ci.yml | 29 ++++++++++ .../src/deploymentagent/Dockerfile | 18 +++--- deploymentagent/src/deploymentagent/main.go | 8 --- deploymentagent/src/deploymentagent/server.go | 57 ++++++++++++++++--- nixos/nix/system.nix | 22 ++++++- 5 files changed, 111 insertions(+), 23 deletions(-) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..4bd82a7 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,29 @@ +image: docker:latest +services: +- docker:dind + +stages: +- build +- release + +variables: + BUILD_IMAGE: registry.gitlab.com/infektweb/glv5/hetzner-cloud-environment/deploymentagent:$CI_COMMIT_REF_NAME + RELEASE_IMAGE: registry.gitlab.com/infektweb/glv5/hetzner-cloud-environment/deploymentagent:latest + +before_script: + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com + +build: + stage: build + script: + - cd deploymentagent/src/deploymentagent && docker build --build-arg CI_JOB_TOKEN=$CI_JOB_TOKEN --pull -t $BUILD_IMAGE . + - docker push $BUILD_IMAGE + +release: + stage: release + script: + - docker pull $BUILD_IMAGE + - docker tag $BUILD_IMAGE $RELEASE_IMAGE + - docker push $RELEASE_IMAGE + only: + - master diff --git a/deploymentagent/src/deploymentagent/Dockerfile b/deploymentagent/src/deploymentagent/Dockerfile index 77f5542..3d181d9 100644 --- a/deploymentagent/src/deploymentagent/Dockerfile +++ b/deploymentagent/src/deploymentagent/Dockerfile @@ -4,17 +4,21 @@ WORKDIR /go/src/app COPY . . -RUN apk add --no-cache openssh git && \ - git config --global url."git@gitlab.com:".insteadOf "https://gitlab.com/" && \ - mkdir /root/.ssh && \ - ssh-keyscan gitlab.com >> /root/.ssh/known_hosts +RUN apk add --no-cache openssh git ARG SSH_KEY ENV SSH_KEY=${SSH_KEY} +ARG CI_JOB_TOKEN +ENV CI_JOB_TOKEN=${CI_JOB_TOKEN} -RUN printf "$SSH_KEY" | base64 -d > /root/.ssh/id_rsa && chmod 600 /root/.ssh/id_rsa && \ - go build -o deploymentagent && \ - rm /root/.ssh/id_rsa +RUN test -n "$SSH_KEY" && \ + { mkdir /root/.ssh && ssh-keyscan gitlab.com >> /root/.ssh/known_hosts && printf "$SSH_KEY" | base64 -d > /root/.ssh/id_rsa && chmod 600 /root/.ssh/id_rsa && git config --global url."git@gitlab.com:".insteadOf "https://gitlab.com/"; } || true + +RUN test -n "$CI_JOB_TOKEN" && \ + { printf "machine gitlab.com\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc; } || true + +RUN go build -o deploymentagent && \ + rm -f /root/.ssh/id_rsa FROM alpine:3.12 diff --git a/deploymentagent/src/deploymentagent/main.go b/deploymentagent/src/deploymentagent/main.go index dbf9c7b..636c3f1 100644 --- a/deploymentagent/src/deploymentagent/main.go +++ b/deploymentagent/src/deploymentagent/main.go @@ -5,17 +5,9 @@ import ( "net/http" ) -var ( - inputQueue *DeploymentQueue // queued - processQueue *DeploymentQueue // deploying, failed, deployed -) - func main() { server := newDeploymentAgentServer() - inputQueue = new(DeploymentQueue) - processQueue = new(DeploymentQueue) - go processDeploymentSpecs(&podmanClient{}, &http.Client{Transport: socketHttpTransport}, "http://podman/v1.0.0/libpod") if err := http.ListenAndServe(":5000", server); err != nil { diff --git a/deploymentagent/src/deploymentagent/server.go b/deploymentagent/src/deploymentagent/server.go index 3403315..d47a970 100644 --- a/deploymentagent/src/deploymentagent/server.go +++ b/deploymentagent/src/deploymentagent/server.go @@ -3,7 +3,9 @@ package main import ( "encoding/json" "fmt" + "io" "io/ioutil" + "net" "net/http" "sort" "time" @@ -16,15 +18,23 @@ const ( ) var ( - basicAuthUsername string - basicAuthPassword string - deploymentStateFile string + basicAuthUsername string + basicAuthPassword string + deploymentStateFile string + nixosRebuildSocketUrl string + inputQueue *DeploymentQueue // queued + processQueue *DeploymentQueue // deploying, failed, deployed ) func init() { basicAuthUsername = settings.GetSetting("BASIC_AUTH_USERNAME", "default") basicAuthPassword = settings.GetSetting("BASIC_AUTH_PASSWORD", "default") deploymentStateFile = settings.GetSetting("DEPLOYMENT_STATE_FILE", "./guidelines.json") + nixosRebuildSocketUrl = settings.GetSetting("NIXOS_REBUILD_SOCKET_URL", "localhost:4444") + + inputQueue = new(DeploymentQueue) + processQueue = new(DeploymentQueue) + } type DeploymentAgentServer struct { @@ -181,19 +191,28 @@ func processDeploymentSpecs(pc *podmanClient, hc *http.Client, url string) { for { time.Sleep(5 * time.Second) if len(inputQueue.GetAll()) > 0 { + var err error + dequequedDeploymentSpec := inputQueue.Dequeue() processQueue.Enqueue(dequequedDeploymentSpec.Id, "deploying", "Deployment in progress", dequequedDeploymentSpec.DeploymentSpec) - err := pc.pullImages(dequequedDeploymentSpec.DeploymentSpec, hc, url) + + err = pc.pullImages(dequequedDeploymentSpec.DeploymentSpec, hc, url) if err != nil { - processQueue.updateStatus(dequequedDeploymentSpec.Id, "failed", err.Error(), nil) + processQueue.updateStatus(dequequedDeploymentSpec.Id, "failed", ("When pulling images with Podman: " + err.Error()), getDeploymentState()) } else { err := mergeDeploymentState(dequequedDeploymentSpec.DeploymentSpec) if err != nil { - processQueue.updateStatus(dequequedDeploymentSpec.Id, "failed", err.Error(), getDeploymentState()) + processQueue.updateStatus(dequequedDeploymentSpec.Id, "failed", ("When updating the deploymentstate file" + err.Error()), getDeploymentState()) } else { - processQueue.updateStatus(dequequedDeploymentSpec.Id, "deployed", "Deployment successful", getDeploymentState()) + err = triggerNixosRebuild() + if err != nil { + processQueue.updateStatus(dequequedDeploymentSpec.Id, "failed", ("When triggering nixos-rebuild with Systemd: " + err.Error()), getDeploymentState()) + } else { + processQueue.updateStatus(dequequedDeploymentSpec.Id, "deployed", "Deployment successful", getDeploymentState()) + } } } + fmt.Println(processQueue.GetAll()) } } @@ -230,3 +249,27 @@ func mergeDeploymentState(deploymentSpec DeploymentSpec) error { return nil } + +// triggerNixosRebuild connects to the activation socket of a +// systemd unit which will trigger the nixos-rebuild unit. +// The connection will be kept open and closed as soon as the +// build has been completed, however, there is no way of +// knowing whether there has been an error in the build phase +// and thus the switch has not been applied. +func triggerNixosRebuild() error { + data := make([]byte, 1) + + c, err := net.Dial("tcp", nixosRebuildSocketUrl) + if err != nil { + return err + } + + if _, err := c.Read(data); err == io.EOF { + fmt.Println("nixos-rebuild switch has finished") + c.Close() + } else { + c.Close() + return err + } + return nil +} diff --git a/nixos/nix/system.nix b/nixos/nix/system.nix index 9992763..0ddd357 100644 --- a/nixos/nix/system.nix +++ b/nixos/nix/system.nix @@ -23,9 +23,29 @@ # `systemctl start nixos-rebuild` := `nixos-rebuild switch` systemd.services.nixos-rebuild = { serviceConfig.Type = "oneshot"; + postStart = "systemctl stop socket-nixos-rebuild-trigger.service && systemctl restart socket-nixos-rebuild-trigger.socket"; script = '' - /run/current-system/sw/bin/nixos-rebuild switch -I nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs -I nixos-config=/etc/nixos/configuration.nix + /run/current-system/sw/bin/nixos-rebuild switch -I nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos/nixpkgs -I nixos-config=/etc/nixos/configuration.nix ''; + + }; + + systemd.services.socket-nixos-rebuild-trigger = { + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + }; + after = [ "socket-nixos-rebuild-trigger.socket" ]; + requires = [ "socket-nixos-rebuild-trigger.socket" ]; + script = '' + systemctl start nixos-rebuild + ''; + }; + + systemd.sockets.socket-nixos-rebuild-trigger = { + listenStreams = [ "0.0.0.0:4444" ]; + partOf = [ "socket-nixos-rebuild-trigger.service" ]; + wantedBy = [ "sockets.target" ]; }; users.extraUsers.operator = {