From b2d4814cb5886ddc2494480267e5775c1635894b Mon Sep 17 00:00:00 2001 From: Yongun Seong Date: Sat, 8 Jun 2024 05:18:25 +0900 Subject: [PATCH] feat: add harbor integration --- .gitignore | 1 + Makefile | 13 +- cmd/sgs-register-harbor/harbor.go | 227 ++++++++++++++++++++++++++++++ cmd/sgs-register-harbor/main.go | 168 ++++++++++++++++++++++ cmd/sgs/main.go | 2 +- default.nix | 17 ++- deploy/test.sh | 25 ++++ deploy/worker-sync.sh | 20 +++ go.mod | 25 +++- go.sum | 53 ++++++- image.nix | 17 ++- worker/worker.go | 26 +--- worker/worker_test.go | 3 +- 13 files changed, 562 insertions(+), 35 deletions(-) create mode 100644 cmd/sgs-register-harbor/harbor.go create mode 100644 cmd/sgs-register-harbor/main.go create mode 100644 deploy/test.sh create mode 100755 deploy/worker-sync.sh diff --git a/.gitignore b/.gitignore index 891273a..7fe9202 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,4 @@ dist /tmp/ /sgs +/sgs-register-harbor diff --git a/Makefile b/Makefile index 4dacc32..5b135c0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ CSS_SRC := view/static/styles.css CSS_DST := view/static/dist/styles.css +BINS := sgs sgs-register-harbor + .PHONY: all all: generate build @@ -14,14 +16,21 @@ generate-tailwindcss: npx postcss $(CSS_SRC) -o $(CSS_DST) .PHONY: build -build: +build: $(BINS) + +.PHONY: sgs +sgs: go build -o ./sgs ./cmd/sgs +.PHONY: sgs-register-harbor +sgs-register-harbor: + go build -o ./sgs-register-harbor ./cmd/sgs-register-harbor + .PHONY: clean clean: rm -f $(CSS_DST) rm -f view/*_templ.go - rm -f sgs + rm -f $(BINS) .PHONY: build-deps build-deps: diff --git a/cmd/sgs-register-harbor/harbor.go b/cmd/sgs-register-harbor/harbor.go new file mode 100644 index 0000000..7725b51 --- /dev/null +++ b/cmd/sgs-register-harbor/harbor.go @@ -0,0 +1,227 @@ +package main + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/go-openapi/runtime" + "github.com/goharbor/go-client/pkg/harbor" + "github.com/goharbor/go-client/pkg/sdk/v2.0/client" + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/member" + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/project" + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/repository" + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/robot" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" +) + +type harborIface interface { + listProjects(ctx context.Context) ([]string, error) + createProject(ctx context.Context, name string) error + deleteProject(ctx context.Context, name string) error + + listMembers(ctx context.Context, name string) ([]harborMember, error) + createMember(ctx context.Context, name string, username string) error + deleteMember(ctx context.Context, name string, memberID int64) error + + createRobot(ctx context.Context, name string) error +} + +// We need the username for creation, ID for deletion. +type harborMember struct { + id int64 + name string +} + +func pointer[T any](v T) *T { + return &v +} + +type harborImpl client.HarborAPI + +func loadHarbor(url, username, password string) (*harborImpl, error) { + clientSet, err := harbor.NewClientSet(&harbor.ClientSetConfig{ + URL: url, + Username: username, + Password: password, + }) + if err != nil { + return nil, err + } + return (*harborImpl)(clientSet.V2()), nil +} + +func (h *harborImpl) listProjects(ctx context.Context) ([]string, error) { + out := []string(nil) + + for page := int64(1); ; page++ { + res, err := h.Project.ListProjects(ctx, &project.ListProjectsParams{ + Page: pointer(page), + PageSize: pointer(int64(10)), + }) + if err != nil { + return nil, err + } + + for _, p := range res.Payload { + out = append(out, p.Name) + } + if int64(len(out)) >= res.XTotalCount { + break + } + } + + return out, nil +} + +func (h harborImpl) createProject(ctx context.Context, name string) error { + _, err := h.Project.CreateProject(ctx, &project.CreateProjectParams{ + Project: &models.ProjectReq{ProjectName: name}, + }) + return err +} + +func (h harborImpl) deleteProject(ctx context.Context, name string) error { + repos := []string(nil) + + for page := int64(1); ; page++ { + res, err := h.Repository.ListRepositories(ctx, &repository.ListRepositoriesParams{ + ProjectName: name, + Page: pointer(page), + PageSize: pointer(int64(10)), + }) + if err != nil { + return err + } + + for _, r := range res.Payload { + repos = append(repos, strings.TrimPrefix(r.Name, name+"/")) + } + + if int64(len(repos)) >= res.XTotalCount { + break + } + } + + for _, repo := range repos { + fmt.Println(repo) + _, err := h.Repository.DeleteRepository(ctx, &repository.DeleteRepositoryParams{ + ProjectName: name, + RepositoryName: repo, + }) + if err != nil { + return err + } + } + + _, err := h.Project.DeleteProject(ctx, &project.DeleteProjectParams{ + XIsResourceName: pointer(true), + ProjectNameOrID: name, + }) + return err +} + +func (h harborImpl) listMembers(ctx context.Context, name string) ([]harborMember, error) { + out := []harborMember(nil) + + for page := int64(1); ; page++ { + res, err := h.Member.ListProjectMembers(ctx, &member.ListProjectMembersParams{ + XIsResourceName: pointer(true), + ProjectNameOrID: name, + Page: pointer(page), + PageSize: pointer(int64(10)), + }) + if err != nil { + return nil, err + } + + for _, m := range res.Payload { + out = append(out, harborMember{m.EntityID, m.EntityName}) + } + + if int64(len(out)) >= res.XTotalCount { + break + } + } + + return out, nil +} + +func (h harborImpl) createMember(ctx context.Context, name string, username string) error { + _, err := h.Member.CreateProjectMember(ctx, &member.CreateProjectMemberParams{ + XIsResourceName: pointer(true), + ProjectNameOrID: name, + ProjectMember: &models.ProjectMember{ + RoleID: 4, // maintainer + MemberUser: &models.UserEntity{Username: username}, + }, + }) + + // As a special case, member creation may fail if + // - the user does not exist / has never logged in to harbor + // - the user is already a member of the project (including the admin) + // We return nil in these cases. + if aerr := (&runtime.APIError{}); errors.As(err, &aerr) && aerr.Code == 404 { + err = nil + } + if err1 := (&member.CreateProjectMemberConflict{}); errors.As(err, &err1) { + err = nil + } + + return err +} + +func (h harborImpl) deleteMember(ctx context.Context, name string, memberID int64) error { + _, err := h.Member.DeleteProjectMember(ctx, &member.DeleteProjectMemberParams{ + XIsResourceName: pointer(true), + ProjectNameOrID: name, + Mid: memberID, + }) + return err +} + +func (h harborImpl) createRobot(ctx context.Context, name string) error { + res, err := h.Robot.CreateRobot(ctx, &robot.CreateRobotParams{ + Robot: &models.RobotCreate{ + Level: "project", + Name: "bacchus-sgs", + Permissions: []*models.RobotPermission{{ + Kind: "project", + Namespace: name, + Access: []*models.Access{{ + Resource: "repository", + Action: "pull", + }}, + }}, + Duration: -1, + }, + }) + if err != nil { + return err + } + + cmd := exec.CommandContext(ctx, + "kubectl", "create", "secret", "docker-registry", + "-n", name, "sgs-registry", + "--docker-server", os.Getenv("SGS_HARBOR_URL"), + "--docker-username", res.Payload.Name, + "--docker-password", res.Payload.Secret, // TODO: avoid passing secret as argument + ) + if err := cmd.Run(); err != nil { + return err + } + + cmd = exec.CommandContext(ctx, + "kubectl", "patch", "serviceaccount", + "-n", name, "default", + "-p", fmt.Sprintf(`{"imagePullSecrets": [{"name": "sgs-registry"}]}`), + ) + if err := cmd.Run(); err != nil { + return err + } + + return nil +} diff --git a/cmd/sgs-register-harbor/main.go b/cmd/sgs-register-harbor/main.go new file mode 100644 index 0000000..7c73f11 --- /dev/null +++ b/cmd/sgs-register-harbor/main.go @@ -0,0 +1,168 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "log/slog" + "os" + "os/signal" + "slices" + + "gopkg.in/yaml.v3" +) + +func main() { + ctx := context.Background() + ctx, stop := signal.NotifyContext(ctx, os.Interrupt) + defer stop() + defer context.AfterFunc(ctx, stop)() + + if err := run(ctx); err != nil { + log.Println(err) + os.Exit(1) + } +} + +type workspace struct { + IDHash string `yaml:"idHash"` + Users []string `yaml:"users"` +} + +func run(ctx context.Context) error { + var want struct { + Workspaces []workspace `yaml:"workspaces"` + } + err := yaml.NewDecoder(os.Stdin).Decode(&want) + if err != nil { + return fmt.Errorf("failed parsing workspace specification: %w", err) + } + + harborURL := os.Getenv("SGS_HARBOR_URL") + if harborURL == "" { + return errors.New("SGS_HARBOR_URL is not set") + } + harborUsername := os.Getenv("SGS_HARBOR_USERNAME") + if harborUsername == "" { + return errors.New("SGS_HARBOR_USERNAME is not set") + } + harborPassword := os.Getenv("SGS_HARBOR_PASSWORD") + if harborPassword == "" { + return errors.New("SGS_HARBOR_PASSWORD is not set") + } + + hapi, err := loadHarbor(harborURL, harborUsername, harborPassword) + if err != nil { + return fmt.Errorf("failed loading harbor client: %w", err) + } + + return runSync(ctx, hapi, want.Workspaces) +} + +func runSync(ctx context.Context, hapi harborIface, want []workspace) error { + var outErr error + + projects, err := hapi.listProjects(ctx) + if err != nil { + return fmt.Errorf("failed listing projects: %w", err) + } + + for _, p := range projects { + ind := slices.IndexFunc(want, func(w workspace) bool { + return fmt.Sprintf("ws-%s", w.IDHash) == p + }) + + if ind == -1 { + // not found, delete project + slog.InfoContext(ctx, fmt.Sprintf("deleting project %q", p)) + if err := hapi.deleteProject(ctx, p); err != nil { + err = fmt.Errorf("failed deleting project %q: %w", p, err) + slog.WarnContext(ctx, err.Error()) + outErr = errors.Join(outErr, err) + } + continue + } + + slog.InfoContext(ctx, fmt.Sprintf("syncing workspace %q", p)) + if err := syncWorkspace(ctx, hapi, want[ind]); err != nil { + err = fmt.Errorf("failed syncing workspace %q: %w", p, err) + slog.WarnContext(ctx, err.Error()) + outErr = errors.Join(outErr, err) + } + + // remove from reference slice + want = slices.Delete(want, ind, ind+1) + } + + // create remaining workspaces + for _, w := range want { + slog.InfoContext(ctx, fmt.Sprintf("creating workspace %q", w.IDHash)) + if err := createWorkspace(ctx, hapi, w); err != nil { + err = fmt.Errorf("failed creating workspace %q: %w", w.IDHash, err) + slog.WarnContext(ctx, err.Error()) + outErr = errors.Join(outErr, err) + } + } + + return outErr +} + +func syncWorkspace(ctx context.Context, hapi harborIface, w workspace) error { + project := fmt.Sprintf("ws-%s", w.IDHash) + + members, err := hapi.listMembers(ctx, project) + if err != nil { + return fmt.Errorf("failed listing members: %w", err) + } + + for _, m := range members { + if m.name == "admin" { + continue + } + + ind := slices.Index(w.Users, m.name) + if ind == -1 { + // not found, delete member + slog.InfoContext(ctx, fmt.Sprintf("deleting user %q from project %q", m.name, project)) + if err := hapi.deleteMember(ctx, project, m.id); err != nil { + return err + } + continue + } + + w.Users = slices.Delete(w.Users, ind, ind+1) + } + + for _, u := range w.Users { + slog.InfoContext(ctx, fmt.Sprintf("adding user %q to project %q", u, project)) + if err := hapi.createMember(ctx, project, u); err != nil { + return err + } + } + + return nil +} + +func createWorkspace(ctx context.Context, hapi harborIface, w workspace) error { + project := fmt.Sprintf("ws-%s", w.IDHash) + + if err := hapi.createProject(ctx, project); err != nil { + return err + } + for _, u := range w.Users { + slog.InfoContext(ctx, fmt.Sprintf("adding user %q to project %q", u, project)) + if err := hapi.createMember(ctx, project, u); err != nil { + return err + } + } + + slog.InfoContext(ctx, fmt.Sprintf("creating robot for project %q", project)) + // TODO: It would be nice to actually sync the robot if not exists on cluster + // but that's hard. + if err := hapi.createRobot(ctx, project); err != nil { + return err + } + + return nil +} diff --git a/cmd/sgs/main.go b/cmd/sgs/main.go index e9e5a7b..4eb2ffd 100644 --- a/cmd/sgs/main.go +++ b/cmd/sgs/main.go @@ -51,7 +51,7 @@ func run(ctx context.Context) error { queue := worker.NewQueue( repo.Workspaces(), - worker.ApplyWorker(cfg.Worker), + worker.CmdWorker(cfg.Worker.Command), time.Minute, 5*time.Second, ) queue.Enqueue() // enqueue update on startup diff --git a/default.nix b/default.nix index 45aba2a..43f6401 100644 --- a/default.nix +++ b/default.nix @@ -25,14 +25,18 @@ buildGoModule rec { name = "sgs"; inherit src; - vendorHash = "sha256-M+/hAVmtksajv/j2zsKcAUeMp813bzihwfvMaeZEhoA="; + vendorHash = "sha256-Wtk0GfNCpTALHVlOl1Al94E/WoS59BcBDmER6SRDcZQ="; ldflags = [ "-s" "-w" ]; - subPackages = [ "cmd/sgs" ]; + subPackages = [ + "cmd/sgs" + "cmd/sgs-register-harbor" + ]; + # test all packages preCheck = '' unset subPackages ''; @@ -62,7 +66,14 @@ buildGoModule rec { ]; postInstall = '' - wrapProgram $out/bin/sgs --prefix PATH : ${lib.makeBinPath buildInputs} + cp -r deploy/chart $out/chart + install -m755 deploy/worker-sync.sh $out/bin/worker-sync.sh + + wrapProgram $out/bin/sgs \ + --prefix PATH : ${lib.makeBinPath buildInputs} \ + --set-default SGS_WORKER_COMMAND "$out/bin/worker-sync.sh" \ + --set-default SGS_DEPLOY_CHART_PATH "$out/chart" \ + --set-default SGS_DEPLOY_REGHARBOR_PATH "$out/bin/sgs-register-harbor" ''; meta.mainProgram = "sgs"; diff --git a/deploy/test.sh b/deploy/test.sh new file mode 100644 index 0000000..14ed620 --- /dev/null +++ b/deploy/test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +export KUBECTL_APPLYSET=true + +input=$(cat) + +cmd_template=( helm template sgs "$SGS_WORKER_CHART_PATH" -f- ) + +cmd_apply=( + kubectl apply + --applyset workspacesets.sgs.snucse.org/sgs + --prune -f- +) +if [[ "$SGS_WORKER_APPLY" != "true" ]]; then + cmd_apply+=( --dry-run=client ) +fi + +cmd_register_harbor=( sgs-register-harbor ) + +<<<"$input" "${cmd_template[@]}" | "${cmd_apply[@]}" + +if [[ "$SGS_WORKER_APPLY" = "true" ]]; then + <<<"$input" "${cmd_register_harbor[@]}" +fi diff --git a/deploy/worker-sync.sh b/deploy/worker-sync.sh new file mode 100755 index 0000000..c5811fe --- /dev/null +++ b/deploy/worker-sync.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +export KUBECTL_APPLYSET=true + +input=$(cat) + +cmd_template=( helm template sgs "$SGS_DEPLOY_CHART_PATH" -f- ) +cmd_apply=( kubectl apply --applyset workspacesets.sgs.snucse.org/sgs --prune -f- ) +cmd_register_harbor=( "$SGS_DEPLOY_REGHARBOR_PATH" ) + +if [[ "$SGS_DEPLOY_APPLY" != "true" ]]; then + cmd_apply+=( --dry-run=client ) +fi + +<<<"$input" "${cmd_template[@]}" | "${cmd_apply[@]}" + +if [[ "$SGS_DEPLOY_APPLY" = "true" ]]; then + <<<"$input" "${cmd_register_harbor[@]}" +fi diff --git a/go.mod b/go.mod index 1b08385..a89a1b5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.22.1 require ( github.com/a-h/templ v0.2.707 github.com/coreos/go-oidc/v3 v3.10.0 + github.com/go-openapi/runtime v0.28.0 + github.com/goharbor/go-client v0.210.0 github.com/golang-migrate/migrate/v4 v4.17.1 github.com/google/go-cmp v0.6.0 github.com/gorilla/sessions v1.2.2 @@ -14,12 +16,26 @@ require ( github.com/labstack/echo/v4 v4.12.0 github.com/spf13/viper v1.19.0 golang.org/x/oauth2 v0.21.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -28,11 +44,15 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -43,6 +63,10 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + go.mongodb.org/mongo-driver v1.15.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.24.0 // indirect @@ -53,5 +77,4 @@ require ( golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a5eea1b..5419203 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U= github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -26,8 +28,35 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goharbor/go-client v0.210.0 h1:QwgLcWNSC3MFhBe7lq3BxDPtKQiD3k6hf6Lt26NChOI= +github.com/goharbor/go-client v0.210.0/go.mod h1:XMWHucuHU9VTRx6U6wYwbRuyCVhE6ffJGRjaeo0nvwo= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= @@ -36,6 +65,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= @@ -59,6 +90,8 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -73,6 +106,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -84,10 +119,14 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -95,8 +134,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -128,6 +167,16 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= +go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/image.nix b/image.nix index 5cafd54..c9841a6 100644 --- a/image.nix +++ b/image.nix @@ -1,13 +1,20 @@ -{ lib, dockerTools, sgs, cacert, tini }: +{ + lib, + dockerTools, + sgs, + cacert, + tini, +}: -let chart = ./deploy/chart; -in dockerTools.buildLayeredImage { +dockerTools.buildLayeredImage { inherit (sgs) name; contents = [ cacert ]; config = { - Entrypoint = [ (lib.getExe tini) "--" ]; + Entrypoint = [ + (lib.getExe tini) + "--" + ]; Cmd = [ (lib.getExe sgs) ]; - Env = [ "SGS_WORKER_CHART_PATH=${chart}" ]; }; } diff --git a/worker/worker.go b/worker/worker.go index 0fc7a5c..e2e2030 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -65,14 +65,14 @@ func toVWorkspaces(wss []*model.Workspace) ValueWorkspaces { return vwss } -func CmdWorker(name string, args ...string) WorkerFunc { +func CmdWorker(command string) WorkerFunc { return func(ctx context.Context, vwss ValueWorkspaces) error { b, err := json.Marshal(vwss) if err != nil { return err } - cmd := exec.CommandContext(ctx, name, args...) + cmd := exec.CommandContext(ctx, "bash", "-c", command) cmd.Stdin = bytes.NewReader(b) out, err := cmd.CombinedOutput() if err != nil { @@ -85,30 +85,16 @@ func CmdWorker(name string, args ...string) WorkerFunc { } type Config struct { - Apply bool `mapstructure:"apply"` - ChartPath string `mapstructure:"chart_path"` + Command string `mapstructure:"command"` } func (c *Config) Bind() { - viper.SetDefault("worker.apply", false) - viper.BindEnv("worker.apply", "SGS_WORKER_APPLY") - viper.BindEnv("worker.chart_path", "SGS_WORKER_CHART_PATH") + viper.BindEnv("worker.command", "SGS_WORKER_COMMAND") } func (c *Config) Validate() error { - if c.ChartPath == "" { - return fmt.Errorf("chart_path is required") + if c.Command == "" { + return fmt.Errorf("command is required") } return nil } - -func ApplyWorker(cfg Config) WorkerFunc { - cmd := "helm template sgs \"$SGS_WORKER_CHART_PATH\" -f -" - applyCmd := "" - if cfg.Apply { - applyCmd = "KUBECTL_APPLYSET=true kubectl apply --applyset workspacesets.sgs.snucse.org/sgs --prune -f -" - } else { - applyCmd = "KUBECTL_APPLYSET=true kubectl apply --applyset workspacesets.sgs.snucse.org/sgs --prune --dry-run=client -f -" - } - return CmdWorker("bash", "-c", fmt.Sprintf("%s | %s", cmd, applyCmd)) -} diff --git a/worker/worker_test.go b/worker/worker_test.go index f5a0eaa..240f819 100644 --- a/worker/worker_test.go +++ b/worker/worker_test.go @@ -3,6 +3,7 @@ package worker import ( "context" "encoding/json" + "fmt" "os" "testing" @@ -24,7 +25,7 @@ func TestCmdWorker(t *testing.T) { }, } - w := CmdWorker("tee", f.Name()) + w := CmdWorker(fmt.Sprintf("tee %s", f.Name())) if err := w.Work(context.Background(), vwss); err != nil { t.Fatal(err) }