From 62d7055108f3d4bd6d2916d18d784a1481a375a2 Mon Sep 17 00:00:00 2001 From: Vadim Bauer Date: Tue, 24 Sep 2024 16:12:27 +0200 Subject: [PATCH] simplified some flows TODO, change PublishImage to support multi arch images --- comments.patch | 177 +++++++++++++++++++++++++++++ dagger.gen.go | 297 ------------------------------------------------- main.go | 62 +++++------ 3 files changed, 203 insertions(+), 333 deletions(-) create mode 100644 comments.patch delete mode 100644 dagger.gen.go diff --git a/comments.patch b/comments.patch new file mode 100644 index 00000000..413ed6a2 --- /dev/null +++ b/comments.patch @@ -0,0 +1,177 @@ +Subject: [PATCH] comments +--- +Index: main.go +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/main.go b/main.go +--- a/main.go (revision 96fa3fa98fb6ceb760e89eb10f1704e753b888ed) ++++ b/main.go (date 1724857880146) +@@ -3,17 +3,16 @@ + import ( + "context" + "fmt" +- "log" +- "strings" + "github.com/goharbor/harbor-cli/internal/dagger" ++ "log" + ) + + const ( +- GO_VERSION = "1.22.5" +- SYFT_VERSION = "v1.9.0" ++ GO_VERSION = "1.22.5" ++ SYFT_VERSION = "v1.9.0" + GORELEASER_VERSION = "v2.1.0" +- APP_NAME = "dagger-harbor-cli" +- PUBLISH_ADDRESS = "demo.goharbor.io/library/harbor-cli:0.0.3" ++ APP_NAME = "dagger-harbor-cli" ++ PUBLISH_ADDRESS = "demo.goharbor.io/library/harbor-cli:0.0.3" + ) + + type HarborCli struct{} +@@ -46,25 +45,31 @@ + WithMountedDirectory("/src", directoryArg). + WithWorkdir("/src"). + WithExec([]string{"golangci-lint", "run", "--timeout", "5m"}) +- + } + +-func (m *HarborCli) BuildHarbor(ctx context.Context, directoryArg *dagger.Directory) *dagger.Directory { ++// Builds the Harbor CLI for multiple OS and architectures ++func (m *HarborCli) Build(ctx context.Context, source *dagger.Directory) *dagger.Directory { ++ + fmt.Println("🛠️ Building with Dagger...") + oses := []string{"linux", "darwin", "windows"} + arches := []string{"amd64", "arm64"} + outputs := dag.Directory() +- golangcont, main_go_path := fetchMainGoPath(ctx, directoryArg) +- + for _, goos := range oses { + for _, goarch := range arches { +- path := fmt.Sprintf("build/%s/%s/", goos, goarch) +- build := golangcont.WithEnvVariable("GOOS", goos). ++ bin_path := fmt.Sprintf("build/%s/%s/", goos, goarch) ++ builder := dag.Container(). ++ From("golang:1.22-alpine"). ++ WithMountedDirectory("/src", source). ++ WithWorkdir("/src"). ++ WithMountedCache("/go/pkg/mod", dag.CacheVolume("go-mod-122-5")). ++ WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). ++ WithMountedCache("/go/build-cache", dag.CacheVolume("go-build-122-5")). ++ WithEnvVariable("GOCACHE", "/go/build-cache"). ++ WithEnvVariable("GOOS", goos). + WithEnvVariable("GOARCH", goarch). +- WithExec([]string{"go", "build", "-o", path + "harbor", main_go_path}) +- ++ WithExec([]string{"go", "build", "-o", bin_path + "harbor", "/src/cmd/harbor/main.go"}) + // Get reference to build output directory in container +- outputs = outputs.WithDirectory(path, build.Directory(path)) ++ outputs = outputs.WithDirectory(bin_path, builder.Directory(bin_path)) + } + } + return outputs +@@ -90,44 +95,26 @@ + log.Println("Release tasks completed successfully 🎉") + } + +-func (m *HarborCli) DockerPublish(ctx context.Context, directoryArg *dagger.Directory, regUsername string, regPassword *dagger.Secret, privateKey *dagger.Secret, cosignPassword *dagger.Secret) string { ++func (m *HarborCli) DockerPublish(ctx context.Context, source *dagger.Directory) string { + +-builder, main_go_path := fetchMainGoPath(ctx, directoryArg) +-builder = builder.WithWorkdir("/src").WithExec([]string{"go", "build", "-o", "harbor", main_go_path}) ++ builder := m.Build(ctx, source) + +-// Create a minimal runtime container +-runtime := dag.Container(). +-From("alpine:latest"). +-WithWorkdir("/root/"). +-WithFile("/root/harbor", builder.File("/src/harbor")). +-WithEntrypoint([]string{"./harbor"}) ++ // Create a minimal runtime container ++ runtime := dag.Container(). ++ From("alpine:latest"). ++ WithWorkdir("/root/"). ++ WithFile("/root/harbor", builder.File("/src/harbor")). ++ WithEntrypoint([]string{"./harbor"}) + +-addr, _ := runtime.Publish(ctx,PUBLISH_ADDRESS) +-_ , err := dag.Cosign().Sign(ctx,privateKey,cosignPassword,[]string{addr},dagger.CosignSignOpts{RegistryUsername: regUsername, RegistryPassword: regPassword}) +-if err != nil { +- panic(err) +-} +-fmt.Printf("Published to %s 🎉\n", addr) +-return addr ++ addr, _ := runtime.Publish(ctx, PUBLISH_ADDRESS) ++ //_, err := dag.Cosign().Sign(ctx, privateKey, cosignPassword, []string{addr}, dagger.CosignSignOpts{RegistryUsername: regUsername, RegistryPassword: regPassword}) ++ //if err != nil { ++ // panic(err) ++ //} ++ fmt.Printf("Published to %s 🎉\n", addr) ++ return addr + } + +-func fetchMainGoPath(ctx context.Context, directoryArg *dagger.Directory) (*dagger.Container, string) { +- +- container := dag.Container(). +- From("golang:1.22-alpine"). +- WithMountedDirectory("/src", directoryArg). +- WithWorkdir("/src"). +- WithExec([]string{"sh", "-c", "export MAIN_GO_PATH=$(find ./cmd -type f -name 'main.go' -print -quit) && echo $MAIN_GO_PATH > main_go_path.txt"}) +- +- // Reading the content of main_go_path.txt file and fetching the actual path of main.go +- main_go_txt_file, _ := container.File("main_go_path.txt").Contents(ctx) +- trimmedPath := strings.TrimPrefix(main_go_txt_file, "./") +- result := "/src/" + trimmedPath +- main_go_path := strings.TrimRight(result, "\n") +- +- return container, main_go_path +-} +- + func goreleaserContainer(directoryArg *dagger.Directory, githubToken string) *dagger.Container { + token := dag.SetSecret("github_token", githubToken) + +Index: dagger.json +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/dagger.json b/dagger.json +--- a/dagger.json (revision 96fa3fa98fb6ceb760e89eb10f1704e753b888ed) ++++ b/dagger.json (date 1724762958963) +@@ -8,5 +8,5 @@ + } + ], + "source": ".", +- "engineVersion": "v0.12.3" ++ "engineVersion": "v0.12.5" + } +Index: README.md +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/README.md b/README.md +--- a/README.md (revision 96fa3fa98fb6ceb760e89eb10f1704e753b888ed) ++++ b/README.md (date 1724851803391) +@@ -102,11 +102,16 @@ + + ## Build From Source + ++For building the Harbor CLI locally, we recommend using [Dagger](https://docs.dagger.io/). ++Make sure you have also a container runtime like Docker or Podman installed on your system. ++ + ```bash + git clone https://github.com/goharbor/harbor-cli.git +-cd harbor-cli/cmd/harbor +-go build . +-sudo mv harbor /usr/local/bin/ ++dagger functions # to see all existing operations without looking into code ++dagger call build --source=. # to create a harbor cli binary ++ls -la build/ # to see the binary for your platform ++ ++ + ``` + + ## Linux and MacOS diff --git a/dagger.gen.go b/dagger.gen.go deleted file mode 100644 index be7ecf6e..00000000 --- a/dagger.gen.go +++ /dev/null @@ -1,297 +0,0 @@ -// Code generated by dagger. DO NOT EDIT. - -package main - -import ( - "context" - "encoding/json" - "fmt" - "log/slog" - "os" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.24.0" - "go.opentelemetry.io/otel/trace" - - "github.com/goharbor/harbor-cli/internal/dagger" - "github.com/goharbor/harbor-cli/internal/querybuilder" - "github.com/goharbor/harbor-cli/internal/telemetry" -) - -var dag = dagger.Connect() - -func Tracer() trace.Tracer { - return otel.Tracer("dagger.io/sdk.go") -} - -// used for local MarshalJSON implementations -var marshalCtx = context.Background() - -// called by main() -func setMarshalContext(ctx context.Context) { - marshalCtx = ctx - dagger.SetMarshalContext(ctx) -} - -type DaggerObject = querybuilder.GraphQLMarshaller - -type ExecError = dagger.ExecError - -// ptr returns a pointer to the given value. -func ptr[T any](v T) *T { - return &v -} - -// convertSlice converts a slice of one type to a slice of another type using a -// converter function -func convertSlice[I any, O any](in []I, f func(I) O) []O { - out := make([]O, len(in)) - for i, v := range in { - out[i] = f(v) - } - return out -} - -func (r HarborCli) MarshalJSON() ([]byte, error) { - var concrete struct{} - return json.Marshal(&concrete) -} - -func (r *HarborCli) UnmarshalJSON(bs []byte) error { - var concrete struct{} - err := json.Unmarshal(bs, &concrete) - if err != nil { - return err - } - return nil -} - -func main() { - ctx := context.Background() - - // Direct slog to the new stderr. This is only for dev time debugging, and - // runtime errors/warnings. - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ - Level: slog.LevelWarn, - }))) - - if err := dispatch(ctx); err != nil { - fmt.Println(err.Error()) - os.Exit(2) - } -} - -func dispatch(ctx context.Context) error { - ctx = telemetry.InitEmbedded(ctx, resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceNameKey.String("dagger-go-sdk"), - // TODO version? - )) - defer telemetry.Close() - - // A lot of the "work" actually happens when we're marshalling the return - // value, which entails getting object IDs, which happens in MarshalJSON, - // which has no ctx argument, so we use this lovely global variable. - setMarshalContext(ctx) - - fnCall := dag.CurrentFunctionCall() - parentName, err := fnCall.ParentName(ctx) - if err != nil { - return fmt.Errorf("get parent name: %w", err) - } - fnName, err := fnCall.Name(ctx) - if err != nil { - return fmt.Errorf("get fn name: %w", err) - } - parentJson, err := fnCall.Parent(ctx) - if err != nil { - return fmt.Errorf("get fn parent: %w", err) - } - fnArgs, err := fnCall.InputArgs(ctx) - if err != nil { - return fmt.Errorf("get fn args: %w", err) - } - - inputArgs := map[string][]byte{} - for _, fnArg := range fnArgs { - argName, err := fnArg.Name(ctx) - if err != nil { - return fmt.Errorf("get fn arg name: %w", err) - } - argValue, err := fnArg.Value(ctx) - if err != nil { - return fmt.Errorf("get fn arg value: %w", err) - } - inputArgs[argName] = []byte(argValue) - } - - result, err := invoke(ctx, []byte(parentJson), parentName, fnName, inputArgs) - if err != nil { - return fmt.Errorf("invoke: %w", err) - } - resultBytes, err := json.Marshal(result) - if err != nil { - return fmt.Errorf("marshal: %w", err) - } - if err = fnCall.ReturnValue(ctx, dagger.JSON(resultBytes)); err != nil { - return fmt.Errorf("store return value: %w", err) - } - return nil -} -func invoke(ctx context.Context, parentJSON []byte, parentName string, fnName string, inputArgs map[string][]byte) (_ any, err error) { - _ = inputArgs - switch parentName { - case "HarborCli": - switch fnName { - case "Build": - var parent HarborCli - err = json.Unmarshal(parentJSON, &parent) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) - } - var source *dagger.Directory - if inputArgs["source"] != nil { - err = json.Unmarshal([]byte(inputArgs["source"]), &source) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg source", err)) - } - } - return (*HarborCli).Build(&parent, ctx, source), nil - case "LintCode": - var parent HarborCli - err = json.Unmarshal(parentJSON, &parent) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) - } - var source *dagger.Directory - if inputArgs["source"] != nil { - err = json.Unmarshal([]byte(inputArgs["source"]), &source) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg source", err)) - } - } - return (*HarborCli).LintCode(&parent, ctx, source), nil - case "PullRequest": - var parent HarborCli - err = json.Unmarshal(parentJSON, &parent) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) - } - var directoryArg *dagger.Directory - if inputArgs["directoryArg"] != nil { - err = json.Unmarshal([]byte(inputArgs["directoryArg"]), &directoryArg) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg directoryArg", err)) - } - } - var githubToken string - if inputArgs["githubToken"] != nil { - err = json.Unmarshal([]byte(inputArgs["githubToken"]), &githubToken) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg githubToken", err)) - } - } - (*HarborCli).PullRequest(&parent, ctx, directoryArg, githubToken) - return nil, nil - case "Release": - var parent HarborCli - err = json.Unmarshal(parentJSON, &parent) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) - } - var directoryArg *dagger.Directory - if inputArgs["directoryArg"] != nil { - err = json.Unmarshal([]byte(inputArgs["directoryArg"]), &directoryArg) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg directoryArg", err)) - } - } - var githubToken string - if inputArgs["githubToken"] != nil { - err = json.Unmarshal([]byte(inputArgs["githubToken"]), &githubToken) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg githubToken", err)) - } - } - (*HarborCli).Release(&parent, ctx, directoryArg, githubToken) - return nil, nil - case "DockerPublish": - var parent HarborCli - err = json.Unmarshal(parentJSON, &parent) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) - } - var directoryArg *dagger.Directory - if inputArgs["directoryArg"] != nil { - err = json.Unmarshal([]byte(inputArgs["directoryArg"]), &directoryArg) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg directoryArg", err)) - } - } - var cosignKey *dagger.Secret - if inputArgs["cosignKey"] != nil { - err = json.Unmarshal([]byte(inputArgs["cosignKey"]), &cosignKey) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg cosignKey", err)) - } - } - var cosignPassword string - if inputArgs["cosignPassword"] != nil { - err = json.Unmarshal([]byte(inputArgs["cosignPassword"]), &cosignPassword) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg cosignPassword", err)) - } - } - var regUsername string - if inputArgs["regUsername"] != nil { - err = json.Unmarshal([]byte(inputArgs["regUsername"]), ®Username) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg regUsername", err)) - } - } - var regPassword string - if inputArgs["regPassword"] != nil { - err = json.Unmarshal([]byte(inputArgs["regPassword"]), ®Password) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg regPassword", err)) - } - } - return (*HarborCli).DockerPublish(&parent, ctx, directoryArg, cosignKey, cosignPassword, regUsername, regPassword), nil - default: - return nil, fmt.Errorf("unknown function %s", fnName) - } - case "": - return dag.Module(). - WithObject( - dag.TypeDef().WithObject("HarborCli"). - WithFunction( - dag.Function("Build", - dag.TypeDef().WithObject("Directory")). - WithArg("source", dag.TypeDef().WithObject("Directory").WithOptional(true), dagger.FunctionWithArgOpts{DefaultPath: "./"})). - WithFunction( - dag.Function("LintCode", - dag.TypeDef().WithObject("Container")). - WithArg("source", dag.TypeDef().WithObject("Directory").WithOptional(true), dagger.FunctionWithArgOpts{DefaultPath: "./"})). - WithFunction( - dag.Function("PullRequest", - dag.TypeDef().WithKind(dagger.VoidKind).WithOptional(true)). - WithArg("directoryArg", dag.TypeDef().WithObject("Directory")). - WithArg("githubToken", dag.TypeDef().WithKind(dagger.StringKind))). - WithFunction( - dag.Function("Release", - dag.TypeDef().WithKind(dagger.VoidKind).WithOptional(true)). - WithArg("directoryArg", dag.TypeDef().WithObject("Directory")). - WithArg("githubToken", dag.TypeDef().WithKind(dagger.StringKind))). - WithFunction( - dag.Function("DockerPublish", - dag.TypeDef().WithKind(dagger.StringKind)). - WithArg("directoryArg", dag.TypeDef().WithObject("Directory")). - WithArg("cosignKey", dag.TypeDef().WithObject("Secret")). - WithArg("cosignPassword", dag.TypeDef().WithKind(dagger.StringKind)). - WithArg("regUsername", dag.TypeDef().WithKind(dagger.StringKind)). - WithArg("regPassword", dag.TypeDef().WithKind(dagger.StringKind)))), nil - default: - return nil, fmt.Errorf("unknown object %s", parentName) - } -} diff --git a/main.go b/main.go index ae3a0908..1a8a7677 100644 --- a/main.go +++ b/main.go @@ -3,10 +3,8 @@ package main import ( "context" "fmt" - "log" - "strings" - "github.com/goharbor/harbor-cli/internal/dagger" + "log" ) const ( @@ -29,26 +27,28 @@ func (m *HarborCli) Build( oses := []string{"linux", "darwin", "windows"} arches := []string{"amd64", "arm64"} outputs := dag.Directory() - golangcont, main_go_path := fetchMainGoPath(ctx, source) - for _, goos := range oses { for _, goarch := range arches { - path := fmt.Sprintf("build/%s/%s/", goos, goarch) - build := golangcont.WithEnvVariable("GOOS", goos). + bin_path := fmt.Sprintf("build/%s/%s/", goos, goarch) + builder := dag.Container(). + From("golang:"+GO_VERSION+"-alpine"). + WithMountedDirectory("/src", source). + WithWorkdir("/src"). WithMountedCache("/go/pkg/mod", dag.CacheVolume("go-mod-"+GO_VERSION)). WithEnvVariable("GOMODCACHE", "/go/pkg/mod"). WithMountedCache("/go/build-cache", dag.CacheVolume("go-build-"+GO_VERSION)). WithEnvVariable("GOCACHE", "/go/build-cache"). + WithEnvVariable("GOOS", goos). WithEnvVariable("GOARCH", goarch). - WithExec([]string{"go", "build", "-o", path + "harbor", main_go_path}) + WithExec([]string{"go", "build", "-o", bin_path + "harbor", "/src/cmd/harbor/main.go"}) // Get reference to build output directory in container - outputs = outputs.WithDirectory(path, build.Directory(path)) + outputs = outputs.WithDirectory(bin_path, builder.Directory(bin_path)) } } return outputs } -func (m *HarborCli) LintCode( +func (m *HarborCli) Lint( ctx context.Context, // +optional // +defaultPath="./" @@ -87,19 +87,26 @@ func (m *HarborCli) Release(ctx context.Context, directoryArg *dagger.Directory, log.Println("Release tasks completed successfully 🎉") } -func (m *HarborCli) DockerPublish(ctx context.Context, directoryArg *dagger.Directory, cosignKey *dagger.Secret, cosignPassword string, regUsername string, regPassword string) string { - - builder, main_go_path := fetchMainGoPath(ctx, directoryArg) - builder = builder.WithWorkdir("/src").WithExec([]string{"go", "build", "-o", "harbor", main_go_path}) - - // Create a minimal runtime container - runtime := dag.Container(). +func (m *HarborCli) PublishImage( + ctx context.Context, + // +optional + // +defaultPath="./" + source *dagger.Directory, + cosignKey *dagger.Secret, + cosignPassword string, + regUsername string, + regPassword string, +) string { + + builder := m.Build(ctx, source) + // Create a minimal cli_runtime container + cli_runtime := dag.Container(). From("alpine:latest"). WithWorkdir("/root/"). - WithFile("/root/harbor", builder.File("/src/harbor")). + WithFile("/root/harbor", builder.File("/")). WithEntrypoint([]string{"./harbor"}) - addr, _ := runtime.Publish(ctx, PUBLISH_ADDRESS) + addr, _ := cli_runtime.Publish(ctx, PUBLISH_ADDRESS) cosign_password := dag.SetSecret("cosign_password", cosignPassword) regpassword := dag.SetSecret("reg_password", regPassword) _, err := dag.Cosign().Sign(ctx, cosignKey, cosign_password, []string{addr}, dagger.CosignSignOpts{RegistryUsername: regUsername, RegistryPassword: regpassword}) @@ -110,23 +117,6 @@ func (m *HarborCli) DockerPublish(ctx context.Context, directoryArg *dagger.Dire return addr } -func fetchMainGoPath(ctx context.Context, directoryArg *dagger.Directory) (*dagger.Container, string) { - - container := dag.Container(). - From("golang:1.22-alpine"). - WithMountedDirectory("/src", directoryArg). - WithWorkdir("/src"). - WithExec([]string{"sh", "-c", "export MAIN_GO_PATH=$(find ./cmd -type f -name 'main.go' -print -quit) && echo $MAIN_GO_PATH > main_go_path.txt"}) - - // Reading the content of main_go_path.txt file and fetching the actual path of main.go - main_go_txt_file, _ := container.File("main_go_path.txt").Contents(ctx) - trimmedPath := strings.TrimPrefix(main_go_txt_file, "./") - result := "/src/" + trimmedPath - main_go_path := strings.TrimRight(result, "\n") - - return container, main_go_path -} - func goreleaserContainer(directoryArg *dagger.Directory, githubToken string) *dagger.Container { token := dag.SetSecret("github_token", githubToken)