diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3a454933 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated +/internal/telemetry/** linguist-generated diff --git a/.github/workflows/docker_publish.yml b/.github/workflows/docker_publish.yml new file mode 100644 index 00000000..6be0df9f --- /dev/null +++ b/.github/workflows/docker_publish.yml @@ -0,0 +1,25 @@ +name: Docker Publish + +on: + push: + branches: [docker-publish] + pull_request: + paths-ignore: + - '*.md' + +jobs: + docker-publish: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Call Docker-Publish Function + uses: dagger/dagger-for-github@v6.1.0 + with: + version: "latest" + verb: call + module: github.com/bishal7679/harbor-cli@v0.6.4 + args: docker-publish --directory-arg=. --cosign-key=${{ secrets.COSIGN_KEY }} --cosign-password=${{ env.COSIGN_PASSWORD }} --reg-username=${{ env.REGISTRY_USERNAME }} --reg-password=${{ env.REGISTRY_PASSWORD }} diff --git a/.gitignore b/.gitignore index 4f6c7b6e..4e00192e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ go.work /harbor dist/ +/dagger.gen.go +/internal/* diff --git a/README.md b/README.md index ac2381e7..1dbc06b4 100644 --- a/README.md +++ b/README.md @@ -100,37 +100,32 @@ Windows | ✅ # Installation -## Build From Source - -```bash -git clone https://github.com/goharbor/harbor-cli.git -cd harbor-cli/cmd/harbor -go build . -sudo mv harbor /usr/local/bin/ -``` ## Linux and MacOS - use `amd64/arm64` as per your system architecture +Homebrew is the recommended way to install Harbor CLI on MacOS and Linux. -```bash -## Linux -tar -xzf harbor_0.0.1_linux_amd64.tar.gz -cd harbor_0.0.1_linux_amd64 -sudo mv harbor /usr/local/bin/ - -## MacOS -tar -xzf harbor_0.0.1_darwin_amd64.tar.gz -cd harbor_0.0.1_darwin_amd64 -sudo mv harbor /usr/local/bin/ -``` ## Windows -```bash +```shell + winget install harbor + ``` + + +# Build From Source + +Make sure you have latest [Dagger](https://docs.dagger.io/) installed in your system. + +```bash +git clone https://github.com/goharbor/harbor-cli.git +dagger call build +``` + + # Community * **Twitter:** [@project_harbor](https://twitter.com/project_harbor) diff --git a/cmd/harbor/root/artifact/list.go b/cmd/harbor/root/artifact/list.go index 37e928fa..22be9278 100644 --- a/cmd/harbor/root/artifact/list.go +++ b/cmd/harbor/root/artifact/list.go @@ -5,6 +5,7 @@ import ( "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/prompt" "github.com/goharbor/harbor-cli/pkg/utils" + artifactViews "github.com/goharbor/harbor-cli/pkg/views/artifact/list" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -30,9 +31,7 @@ func ListArtifactCommand() *cobra.Command { if err != nil { log.Errorf("failed to list artifacts: %v", err) } - - log.Infof("Artifacts: %v", resp) - + artifactViews.ListArtifacts(resp.Payload) }, } diff --git a/cmd/harbor/root/project/create.go b/cmd/harbor/root/project/create.go index 275e4bc2..9366e518 100644 --- a/cmd/harbor/root/project/create.go +++ b/cmd/harbor/root/project/create.go @@ -14,9 +14,8 @@ func CreateProjectCommand() *cobra.Command { cmd := &cobra.Command{ Use: "create [project name]", Short: "create project", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - opts.ProjectName = args[0] var err error createView := &create.CreateView{ ProjectName: opts.ProjectName, @@ -28,8 +27,6 @@ func CreateProjectCommand() *cobra.Command { if len(args) > 0 { opts.ProjectName = args[0] err = api.CreateProject(opts) - } else if opts.ProjectName != "" && opts.RegistryID != "" && opts.StorageLimit != "" { - err = api.CreateProject(opts) } else { err = createProjectView(createView) } diff --git a/cmd/harbor/root/user/elevate.go b/cmd/harbor/root/user/elevate.go index 9369b9c7..25cc7299 100644 --- a/cmd/harbor/root/user/elevate.go +++ b/cmd/harbor/root/user/elevate.go @@ -5,6 +5,7 @@ import ( "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/views" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -25,10 +26,12 @@ func ElevateUserCmd() *cobra.Command { userId = prompt.GetUserIdFromUser() } - // Todo : Ask for the confirmation before elevating the user to admin role - - err = api.ElevateUser(userId) - + confirm, err := views.ConfirmElevation() + if confirm { + err = api.ElevateUser(userId) + } else { + log.Error("Permission denied for elevate user to admin.") + } if err != nil { log.Errorf("failed to elevate user: %v", err) } diff --git a/dagger.gen.go b/dagger.gen.go deleted file mode 100644 index 72620bff..00000000 --- a/dagger.gen.go +++ /dev/null @@ -1,221 +0,0 @@ -// Code generated by dagger. DO NOT EDIT. - -package main - -import ( - "context" - "encoding/json" - "fmt" - "log/slog" - "os" - - "github.com/goharbor/harbor-cli/internal/dagger" - "github.com/goharbor/harbor-cli/internal/telemetry" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" - "go.opentelemetry.io/otel/trace" -) - -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 = dagger.DaggerObject - -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 "Echo": - var parent HarborCli - err = json.Unmarshal(parentJSON, &parent) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) - } - var stringArg string - if inputArgs["stringArg"] != nil { - err = json.Unmarshal([]byte(inputArgs["stringArg"]), &stringArg) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg stringArg", err)) - } - } - return (*HarborCli).Echo(&parent, stringArg), nil - case "ContainerEcho": - var parent HarborCli - err = json.Unmarshal(parentJSON, &parent) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) - } - var stringArg string - if inputArgs["stringArg"] != nil { - err = json.Unmarshal([]byte(inputArgs["stringArg"]), &stringArg) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg stringArg", err)) - } - } - return (*HarborCli).ContainerEcho(&parent, stringArg), nil - case "GrepDir": - 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 pattern string - if inputArgs["pattern"] != nil { - err = json.Unmarshal([]byte(inputArgs["pattern"]), &pattern) - if err != nil { - panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg pattern", err)) - } - } - return (*HarborCli).GrepDir(&parent, ctx, directoryArg, pattern) - default: - return nil, fmt.Errorf("unknown function %s", fnName) - } - case "": - return dag.Module(). - WithDescription("A generated module for HarborCli functions\n\nThis module has been generated via dagger init and serves as a reference to\nbasic module structure as you get started with Dagger.\n\nTwo functions have been pre-created. You can modify, delete, or add to them,\nas needed. They demonstrate usage of arguments and return types using simple\necho and grep commands. The functions can be called from the dagger CLI or\nfrom one of the SDKs.\n\nThe first line in this comment block is a short description line and the\nrest is a long description with more detail on the module's purpose or usage,\nif appropriate. All modules should have a short description.\n"). - WithObject( - dag.TypeDef().WithObject("HarborCli"). - WithFunction( - dag.Function("Echo", - dag.TypeDef().WithKind(dagger.StringKind)). - WithArg("stringArg", dag.TypeDef().WithKind(dagger.StringKind))). - WithFunction( - dag.Function("ContainerEcho", - dag.TypeDef().WithObject("Container")). - WithDescription("Returns a container that echoes whatever string argument is provided"). - WithArg("stringArg", dag.TypeDef().WithKind(dagger.StringKind))). - WithFunction( - dag.Function("GrepDir", - dag.TypeDef().WithKind(dagger.StringKind)). - WithDescription("Returns lines that match a pattern in the files of the provided Directory"). - WithArg("directoryArg", dag.TypeDef().WithObject("Directory")). - WithArg("pattern", dag.TypeDef().WithKind(dagger.StringKind)))), nil - default: - return nil, fmt.Errorf("unknown object %s", parentName) - } -} diff --git a/dagger.json b/dagger.json index b857c505..1eef0255 100644 --- a/dagger.json +++ b/dagger.json @@ -1,5 +1,12 @@ { "name": "harbor-cli", "sdk": "go", - "engineVersion": "v0.12.3" + "dependencies": [ + { + "name": "cosign", + "source": "github.com/scottames/daggerverse/cosign@8359122a7b90f2c8c6f3165570fdcbec6e923023" + } + ], + "source": ".", + "engineVersion": "v0.13.3" } diff --git a/go.mod b/go.mod index c530436e..5267b01a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.8.0 ) require ( @@ -74,7 +74,7 @@ require ( github.com/go-openapi/validate v0.24.0 // indirect github.com/goharbor/go-client v0.210.0 github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -98,11 +98,19 @@ require ( go.opentelemetry.io/otel/sdk/log v0.4.0 go.opentelemetry.io/otel/trace v1.28.0 go.opentelemetry.io/proto/otlp v1.3.1 - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 // indirect ) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.3.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.3.0 diff --git a/go.sum b/go.sum index 0c1353d1..4dc6e2f2 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 h1:CWyXh/jylQWp2dtiV33mY4iSSp6yf4lmn+c7/tN+ObI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0/go.mod h1:nCLIt0w3Ept2NwF8ThLmrppXsfT07oC8k0XNDxd8sVU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -182,24 +182,24 @@ go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4B go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240726164100-16a4e332762c h1:KqiIx/gBzoTWk+2aWIVE4Uct6yDk6bN8AmSohSMNVRg= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240726164100-16a4e332762c/go.mod h1:6U8l6PZAmnFSxIgHa3LRBpM9dScM77veAD+FUalfy9M= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0 h1:zBPZAISA9NOc5cE8zydqDiS0itvg/P/0Hn9m72a5gvM= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0/go.mod h1:gcj2fFjEsqpV3fXuzAA+0Ze1p2/4MJ4T7d77AmkvueQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 h1:oM0GTNKGlc5qHctWeIGTVyda4iFFalOzMZ3Ehj5rwB4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88/go.mod h1:JGG8ebaMO5nXOPnvKEl+DiA4MGwFjCbjsxT1WHIEBPY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 h1:ccBrA8nCY5mM0y5uO7FT0ze4S0TuFcWdDB2FxGMTjkI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0/go.mod h1:/9pb6634zi2Lk8LYg9Q0X8Ar6jka4dkFOylBLbVQPCE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= -go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= -go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= +go.opentelemetry.io/otel/log v0.3.0 h1:kJRFkpUFYtny37NQzL386WbznUByZx186DpEMKhEGZs= +go.opentelemetry.io/otel/log v0.3.0/go.mod h1:ziCwqZr9soYDwGNbIL+6kAvQC+ANvjgG367HVcyR/ys= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= -go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= +go.opentelemetry.io/otel/sdk/log v0.3.0 h1:GEjJ8iftz2l+XO1GF2856r7yYVh74URiF9JMcAacr5U= +go.opentelemetry.io/otel/sdk/log v0.3.0/go.mod h1:BwCxtmux6ACLuys1wlbc0+vGBd+xytjmjajwqqIul2g= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -210,21 +210,21 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk= -google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f h1:RARaIm8pxYuxyNPbBQf5igT7XdOyCNtat1qAT2ZxjU4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= diff --git a/internal/dagger/dagger.gen.go b/internal/dagger/dagger.gen.go index f1f47c8a..df14f551 100644 --- a/internal/dagger/dagger.gen.go +++ b/internal/dagger/dagger.gen.go @@ -49,7 +49,7 @@ func assertNotNil(argName string, value any) { } } -type DaggerObject querybuilder.GraphQLMarshaller +type DaggerObject = querybuilder.GraphQLMarshaller // getCustomError parses a GraphQL error into a more specific error type. func getCustomError(err error) error { @@ -128,6 +128,9 @@ type CacheVolumeID string // The `ContainerID` scalar type represents an identifier for an object of type Container. type ContainerID string +// The `CosignID` scalar type represents an identifier for an object of type Cosign. +type CosignID string + // The `CurrentModuleID` scalar type represents an identifier for an object of type CurrentModule. type CurrentModuleID string @@ -352,6 +355,7 @@ type Container struct { stderr *string stdout *string sync *ContainerID + up *Void user *string workdir *string } @@ -816,34 +820,6 @@ func (r *Container) Mounts(ctx context.Context) ([]string, error) { return response, q.Execute(ctx) } -// ContainerPipelineOpts contains options for Container.Pipeline -type ContainerPipelineOpts struct { - // Description of the sub-pipeline. - Description string - // Labels to apply to the sub-pipeline. - Labels []PipelineLabel -} - -// Creates a named sub-pipeline. -func (r *Container) Pipeline(name string, opts ...ContainerPipelineOpts) *Container { - q := r.query.Select("pipeline") - for i := len(opts) - 1; i >= 0; i-- { - // `description` optional argument - if !querybuilder.IsZeroValue(opts[i].Description) { - q = q.Arg("description", opts[i].Description) - } - // `labels` optional argument - if !querybuilder.IsZeroValue(opts[i].Labels) { - q = q.Arg("labels", opts[i].Labels) - } - } - q = q.Arg("name", name) - - return &Container{ - query: q, - } -} - // The platform this container executes and publishes as. func (r *Container) Platform(ctx context.Context) (Platform, error) { if r.platform != nil { @@ -994,6 +970,38 @@ func (r *Container) Terminal(opts ...ContainerTerminalOpts) *Container { } } +// ContainerUpOpts contains options for Container.Up +type ContainerUpOpts struct { + // List of frontend/backend port mappings to forward. + // + // Frontend is the port accepting traffic on the host, backend is the service port. + Ports []PortForward + // Bind each tunnel port to a random port on the host. + Random bool +} + +// Starts a Service and creates a tunnel that forwards traffic from the caller's network to that service. +// +// Be sure to set any exposed ports before calling this api. +func (r *Container) Up(ctx context.Context, opts ...ContainerUpOpts) error { + if r.up != nil { + return nil + } + q := r.query.Select("up") + for i := len(opts) - 1; i >= 0; i-- { + // `ports` optional argument + if !querybuilder.IsZeroValue(opts[i].Ports) { + q = q.Arg("ports", opts[i].Ports) + } + // `random` optional argument + if !querybuilder.IsZeroValue(opts[i].Random) { + q = q.Arg("random", opts[i].Random) + } + } + + return q.Execute(ctx) +} + // Retrieves the user to be set for all commands. func (r *Container) User(ctx context.Context) (string, error) { if r.user != nil { @@ -1007,6 +1015,17 @@ func (r *Container) User(ctx context.Context) (string, error) { return response, q.Execute(ctx) } +// Retrieves this container plus the given OCI anotation. +func (r *Container) WithAnnotation(name string, value string) *Container { + q := r.query.Select("withAnnotation") + q = q.Arg("name", name) + q = q.Arg("value", value) + + return &Container{ + query: q, + } +} + // Configures default arguments for future commands. func (r *Container) WithDefaultArgs(args []string) *Container { q := r.query.Select("withDefaultArgs") @@ -1134,8 +1153,6 @@ func (r *Container) WithEnvVariable(name string, value string, opts ...Container // ContainerWithExecOpts contains options for Container.WithExec type ContainerWithExecOpts struct { - // DEPRECATED: For true this can be removed. For false, use `useEntrypoint` instead. - SkipEntrypoint bool // If the container has an entrypoint, prepend it to the args. UseEntrypoint bool // Content to write to the command's standard input before closing (e.g., "Hello world"). @@ -1156,10 +1173,6 @@ type ContainerWithExecOpts struct { func (r *Container) WithExec(args []string, opts ...ContainerWithExecOpts) *Container { q := r.query.Select("withExec") for i := len(opts) - 1; i >= 0; i-- { - // `skipEntrypoint` optional argument - if !querybuilder.IsZeroValue(opts[i].SkipEntrypoint) { - q = q.Arg("skipEntrypoint", opts[i].SkipEntrypoint) - } // `useEntrypoint` optional argument if !querybuilder.IsZeroValue(opts[i].UseEntrypoint) { q = q.Arg("useEntrypoint", opts[i].UseEntrypoint) @@ -1598,6 +1611,16 @@ func (r *Container) WithWorkdir(path string) *Container { } } +// Retrieves this container minus the given OCI annotation. +func (r *Container) WithoutAnnotation(name string) *Container { + q := r.query.Select("withoutAnnotation") + q = q.Arg("name", name) + + return &Container{ + query: q, + } +} + // Retrieves this container with unset default arguments for future commands. func (r *Container) WithoutDefaultArgs() *Container { q := r.query.Select("withoutDefaultArgs") @@ -1680,6 +1703,16 @@ func (r *Container) WithoutFile(path string) *Container { } } +// Retrieves this container with the files at the given paths removed. +func (r *Container) WithoutFiles(paths []string) *Container { + q := r.query.Select("withoutFiles") + q = q.Arg("paths", paths) + + return &Container{ + query: q, + } +} + // Indicate that subsequent operations should not be featured more prominently in the UI. // // This is the initial state of all containers. @@ -1776,6 +1809,134 @@ func (r *Container) Workdir(ctx context.Context) (string, error) { return response, q.Execute(ctx) } +// Cosign represents the cosign Dagger module type +type Cosign struct { + query *querybuilder.Selection + + id *CosignID +} + +func (r *Cosign) WithGraphQLQuery(q *querybuilder.Selection) *Cosign { + return &Cosign{ + query: q, + } +} + +// A unique identifier for this Cosign. +func (r *Cosign) ID(ctx context.Context) (CosignID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response CosignID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Cosign) XXX_GraphQLType() string { + return "Cosign" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Cosign) XXX_GraphQLIDType() string { + return "CosignID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Cosign) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Cosign) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Cosign) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = *dag.LoadCosignFromID(CosignID(id)) + return nil +} + +// CosignSignOpts contains options for Cosign.Sign +type CosignSignOpts struct { + // + // registry username + // + RegistryUsername string + // + // name of the image + // + RegistryPassword *Secret + // + // Docker config + // + DockerConfig *File + // + // Cosign container image + // + CosignImage string + // + // Cosign container image user + // + CosignUser string +} + +// Sign will run cosign from the image, as defined by the cosignImage +// parameter, to sign the given Container image digests +// +// Note: keyless signing not supported as-is +// +// See https://edu.chainguard.dev/open-source/sigstore/cosign/an-introduction-to-cosign/ +func (r *Cosign) Sign(ctx context.Context, privateKey *Secret, password *Secret, digests []string, opts ...CosignSignOpts) ([]string, error) { + assertNotNil("privateKey", privateKey) + assertNotNil("password", password) + q := r.query.Select("sign") + for i := len(opts) - 1; i >= 0; i-- { + // `registryUsername` optional argument + if !querybuilder.IsZeroValue(opts[i].RegistryUsername) { + q = q.Arg("registryUsername", opts[i].RegistryUsername) + } + // `registryPassword` optional argument + if !querybuilder.IsZeroValue(opts[i].RegistryPassword) { + q = q.Arg("registryPassword", opts[i].RegistryPassword) + } + // `dockerConfig` optional argument + if !querybuilder.IsZeroValue(opts[i].DockerConfig) { + q = q.Arg("dockerConfig", opts[i].DockerConfig) + } + // `cosignImage` optional argument + if !querybuilder.IsZeroValue(opts[i].CosignImage) { + q = q.Arg("cosignImage", opts[i].CosignImage) + } + // `cosignUser` optional argument + if !querybuilder.IsZeroValue(opts[i].CosignUser) { + q = q.Arg("cosignUser", opts[i].CosignUser) + } + } + q = q.Arg("privateKey", privateKey) + q = q.Arg("password", password) + q = q.Arg("digests", digests) + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // Reflective module API provided to functions at runtime. type CurrentModule struct { query *querybuilder.Selection @@ -2254,6 +2415,7 @@ func (r *DaggerEngineCacheEntrySet) UnmarshalJSON(bs []byte) error { type Directory struct { query *querybuilder.Selection + digest *string export *string id *DirectoryID sync *DirectoryID @@ -2315,6 +2477,19 @@ func (r *Directory) Diff(other *Directory) *Directory { } } +// Return the directory's digest. The format of the digest is not guaranteed to be stable between releases of Dagger. It is guaranteed to be stable between invocations of the same Dagger engine. +func (r *Directory) Digest(ctx context.Context) (string, error) { + if r.digest != nil { + return *r.digest, nil + } + q := r.query.Select("digest") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // Retrieves a directory at the given path. func (r *Directory) Directory(path string) *Directory { q := r.query.Select("directory") @@ -2490,34 +2665,6 @@ func (r *Directory) UnmarshalJSON(bs []byte) error { return nil } -// DirectoryPipelineOpts contains options for Directory.Pipeline -type DirectoryPipelineOpts struct { - // Description of the sub-pipeline. - Description string - // Labels to apply to the sub-pipeline. - Labels []PipelineLabel -} - -// Creates a named sub-pipeline. -func (r *Directory) Pipeline(name string, opts ...DirectoryPipelineOpts) *Directory { - q := r.query.Select("pipeline") - for i := len(opts) - 1; i >= 0; i-- { - // `description` optional argument - if !querybuilder.IsZeroValue(opts[i].Description) { - q = q.Arg("description", opts[i].Description) - } - // `labels` optional argument - if !querybuilder.IsZeroValue(opts[i].Labels) { - q = q.Arg("labels", opts[i].Labels) - } - } - q = q.Arg("name", name) - - return &Directory{ - query: q, - } -} - // Force evaluation in the engine. func (r *Directory) Sync(ctx context.Context) (*Directory, error) { q := r.query.Select("sync") @@ -2724,6 +2871,16 @@ func (r *Directory) WithoutFile(path string) *Directory { } } +// Retrieves this directory with the files at the given paths removed. +func (r *Directory) WithoutFiles(paths []string) *Directory { + q := r.query.Select("withoutFiles") + q = q.Arg("paths", paths) + + return &Directory{ + query: q, + } +} + // A definition of a custom enum defined in a Module. type EnumTypeDef struct { query *querybuilder.Selection @@ -3147,6 +3304,7 @@ type File struct { query *querybuilder.Selection contents *string + digest *string export *string id *FileID name *string @@ -3181,6 +3339,31 @@ func (r *File) Contents(ctx context.Context) (string, error) { return response, q.Execute(ctx) } +// FileDigestOpts contains options for File.Digest +type FileDigestOpts struct { + // If true, exclude metadata from the digest. + ExcludeMetadata bool +} + +// Return the file's digest. The format of the digest is not guaranteed to be stable between releases of Dagger. It is guaranteed to be stable between invocations of the same Dagger engine. +func (r *File) Digest(ctx context.Context, opts ...FileDigestOpts) (string, error) { + if r.digest != nil { + return *r.digest, nil + } + q := r.query.Select("digest") + for i := len(opts) - 1; i >= 0; i-- { + // `excludeMetadata` optional argument + if !querybuilder.IsZeroValue(opts[i].ExcludeMetadata) { + q = q.Arg("excludeMetadata", opts[i].ExcludeMetadata) + } + } + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // FileExportOpts contains options for File.Export type FileExportOpts struct { // If allowParentDirPath is true, the path argument can be a directory path, in which case the file will be created in that directory. @@ -3463,6 +3646,10 @@ type FunctionWithArgOpts struct { Description string // A default value to use for this argument if not explicitly set by the caller, if any DefaultValue JSON + // If the argument is a Directory or File type, default to load path from context directory, relative to root directory. + DefaultPath string + // Patterns to ignore when loading the contextual argument value. + Ignore []string } // Returns the function with the provided argument @@ -3478,6 +3665,14 @@ func (r *Function) WithArg(name string, typeDef *TypeDef, opts ...FunctionWithAr if !querybuilder.IsZeroValue(opts[i].DefaultValue) { q = q.Arg("defaultValue", opts[i].DefaultValue) } + // `defaultPath` optional argument + if !querybuilder.IsZeroValue(opts[i].DefaultPath) { + q = q.Arg("defaultPath", opts[i].DefaultPath) + } + // `ignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Ignore) { + q = q.Arg("ignore", opts[i].Ignore) + } } q = q.Arg("name", name) q = q.Arg("typeDef", typeDef) @@ -3503,6 +3698,7 @@ func (r *Function) WithDescription(description string) *Function { type FunctionArg struct { query *querybuilder.Selection + defaultPath *string defaultValue *JSON description *string id *FunctionArgID @@ -3515,6 +3711,19 @@ func (r *FunctionArg) WithGraphQLQuery(q *querybuilder.Selection) *FunctionArg { } } +// Only applies to arguments of type File or Directory. If the argument is not set, load it from the given path in the context directory +func (r *FunctionArg) DefaultPath(ctx context.Context) (string, error) { + if r.defaultPath != nil { + return *r.defaultPath, nil + } + q := r.query.Select("defaultPath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // A default value to use for this argument when not explicitly set by the caller, if any. func (r *FunctionArg) DefaultValue(ctx context.Context) (JSON, error) { if r.defaultValue != nil { @@ -3590,6 +3799,16 @@ func (r *FunctionArg) UnmarshalJSON(bs []byte) error { return nil } +// Only applies to arguments of type Directory. The ignore patterns are applied to the input directory, and matching entries are filtered out, in a cache-efficient manner. +func (r *FunctionArg) Ignore(ctx context.Context) ([]string, error) { + q := r.query.Select("ignore") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // The name of the argument in lowerCamelCase format. func (r *FunctionArg) Name(ctx context.Context) (string, error) { if r.name != nil { @@ -3974,8 +4193,9 @@ func (r *GeneratedCode) WithVCSIgnoredPaths(paths []string) *GeneratedCode { type GitModuleSource struct { query *querybuilder.Selection - cloneURL *string + cloneRef *string commit *string + htmlRepoURL *string htmlURL *string id *GitModuleSourceID root *string @@ -3989,12 +4209,12 @@ func (r *GitModuleSource) WithGraphQLQuery(q *querybuilder.Selection) *GitModule } } -// The URL to clone the root of the git repo from -func (r *GitModuleSource) CloneURL(ctx context.Context) (string, error) { - if r.cloneURL != nil { - return *r.cloneURL, nil +// The ref to clone the root of the git repo from +func (r *GitModuleSource) CloneRef(ctx context.Context) (string, error) { + if r.cloneRef != nil { + return *r.cloneRef, nil } - q := r.query.Select("cloneURL") + q := r.query.Select("cloneRef") var response string @@ -4024,6 +4244,19 @@ func (r *GitModuleSource) ContextDirectory() *Directory { } } +// The URL to access the web view of the repository (e.g., GitHub, GitLab, Bitbucket) +func (r *GitModuleSource) HTMLRepoURL(ctx context.Context) (string, error) { + if r.htmlRepoURL != nil { + return *r.htmlRepoURL, nil + } + q := r.query.Select("htmlRepoURL") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // The URL to the source's git repo in a web browser func (r *GitModuleSource) HTMLURL(ctx context.Context) (string, error) { if r.htmlURL != nil { @@ -4788,6 +5021,7 @@ type LocalModuleSource struct { query *querybuilder.Selection id *LocalModuleSourceID + relHostPath *string rootSubpath *string } @@ -4855,6 +5089,19 @@ func (r *LocalModuleSource) UnmarshalJSON(bs []byte) error { return nil } +// The relative path to the module root from the host directory +func (r *LocalModuleSource) RelHostPath(ctx context.Context) (string, error) { + if r.relHostPath != nil { + return *r.relHostPath, nil + } + q := r.query.Select("relHostPath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // The path to the root of the module source under the context directory. This directory contains its configuration file. It also contains its source code (possibly as a subdirectory). func (r *LocalModuleSource) RootSubpath(ctx context.Context) (string, error) { if r.rootSubpath != nil { @@ -5360,6 +5607,7 @@ type ModuleSource struct { asString *string configExists *bool + digest *string id *ModuleSourceID kind *ModuleSourceKind moduleName *string @@ -5490,6 +5738,19 @@ func (r *ModuleSource) Dependencies(ctx context.Context) ([]ModuleDependency, er return convert(response), nil } +// Return the module source's content digest. The format of the digest is not guaranteed to be stable between releases of Dagger. It is guaranteed to be stable between invocations of the same Dagger engine. +func (r *ModuleSource) Digest(ctx context.Context) (string, error) { + if r.digest != nil { + return *r.digest, nil + } + q := r.query.Select("digest") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + // The directory containing the module configuration and source code (source code may be in a subdir). func (r *ModuleSource) Directory(path string) *Directory { q := r.query.Select("directory") @@ -5733,6 +5994,27 @@ func (r *ModuleSource) WithDependencies(dependencies []*ModuleDependency) *Modul } } +// ModuleSourceWithInitOpts contains options for ModuleSource.WithInit +type ModuleSourceWithInitOpts struct { + // Merge module dependencies into the current project's + Merge bool +} + +// Sets module init arguments +func (r *ModuleSource) WithInit(opts ...ModuleSourceWithInitOpts) *ModuleSource { + q := r.query.Select("withInit") + for i := len(opts) - 1; i >= 0; i-- { + // `merge` optional argument + if !querybuilder.IsZeroValue(opts[i].Merge) { + q = q.Arg("merge", opts[i].Merge) + } + } + + return &ModuleSource{ + query: q, + } +} + // Update the module source with a new name. func (r *ModuleSource) WithName(name string) *ModuleSource { q := r.query.Select("withName") @@ -6157,15 +6439,6 @@ func (r *Port) Protocol(ctx context.Context) (NetworkProtocol, error) { return response, q.Execute(ctx) } -type WithClientFunc func(r *Client) *Client - -// With calls the provided function with current Client. -// -// This is useful for reusability and readability by not breaking the calling chain. -func (r *Client) With(f WithClientFunc) *Client { - return f(r) -} - func (r *Client) WithGraphQLQuery(q *querybuilder.Selection) *Client { return &Client{ query: q, @@ -6229,6 +6502,14 @@ func (r *Client) Container(opts ...ContainerOpts) *Container { } } +func (r *Client) Cosign() *Cosign { + q := r.query.Select("cosign") + + return &Cosign{ + query: q, + } +} + // The FunctionCall context that the SDK caller is currently executing in. // // If the caller is not currently executing in a function, this will return an error. @@ -6406,6 +6687,16 @@ func (r *Client) LoadContainerFromID(id ContainerID) *Container { } } +// Load a Cosign from its ID. +func (r *Client) LoadCosignFromID(id CosignID) *Cosign { + q := r.query.Select("loadCosignFromID") + q = q.Arg("id", id) + + return &Cosign{ + query: q, + } +} + // Load a CurrentModule from its ID. func (r *Client) LoadCurrentModuleFromID(id CurrentModuleID) *CurrentModule { q := r.query.Select("loadCurrentModuleFromID") @@ -6792,6 +7083,8 @@ func (r *Client) ModuleDependency(source *ModuleSource, opts ...ModuleDependency type ModuleSourceOpts struct { // If true, enforce that the source is a stable version for source kinds that support versioning. Stable bool + // The relative path to the module root from the host directory + RelHostPath string } // Create a new module source instance from a source ref string. @@ -6802,6 +7095,10 @@ func (r *Client) ModuleSource(refString string, opts ...ModuleSourceOpts) *Modul if !querybuilder.IsZeroValue(opts[i].Stable) { q = q.Arg("stable", opts[i].Stable) } + // `relHostPath` optional argument + if !querybuilder.IsZeroValue(opts[i].RelHostPath) { + q = q.Arg("relHostPath", opts[i].RelHostPath) + } } q = q.Arg("refString", refString) @@ -6810,35 +7107,6 @@ func (r *Client) ModuleSource(refString string, opts ...ModuleSourceOpts) *Modul } } -// PipelineOpts contains options for Client.Pipeline -type PipelineOpts struct { - // Description of the sub-pipeline. - Description string - // Labels to apply to the sub-pipeline. - Labels []PipelineLabel -} - -// Creates a named sub-pipeline. -func (r *Client) Pipeline(name string, opts ...PipelineOpts) *Client { - q := r.query.Select("pipeline") - for i := len(opts) - 1; i >= 0; i-- { - // `description` optional argument - if !querybuilder.IsZeroValue(opts[i].Description) { - q = q.Arg("description", opts[i].Description) - } - // `labels` optional argument - if !querybuilder.IsZeroValue(opts[i].Labels) { - q = q.Arg("labels", opts[i].Labels) - } - } - q = q.Arg("name", name) - - return &Client{ - query: q, - client: r.client, - } -} - // SecretOpts contains options for Client.Secret type SecretOpts struct { Accessor string @@ -7943,7 +8211,7 @@ func getClientParams() (graphql.Client, *querybuilder.Selection) { r = r.WithContext(fallbackSpanContext(r.Context())) // propagate span context via headers (i.e. for Dagger-in-Dagger) - otel.GetTextMapPropagator().Inject(r.Context(), propagation.HeaderCarrier(r.Header)) + telemetry.Propagator.Inject(r.Context(), propagation.HeaderCarrier(r.Header)) return dialTransport.RoundTrip(r) }), @@ -7957,7 +8225,7 @@ func fallbackSpanContext(ctx context.Context) context.Context { if trace.SpanContextFromContext(ctx).IsValid() { return ctx } - return otel.GetTextMapPropagator().Extract(ctx, telemetry.NewEnvCarrier(true)) + return telemetry.Propagator.Extract(ctx, telemetry.NewEnvCarrier(true)) } // TODO: pollutes namespace, move to non internal package in dagger.io/dagger diff --git a/internal/querybuilder/querybuilder.go b/internal/querybuilder/querybuilder.go index f57b134c..2f5acc97 100644 --- a/internal/querybuilder/querybuilder.go +++ b/internal/querybuilder/querybuilder.go @@ -17,10 +17,11 @@ func Query() *Selection { } type Selection struct { - name string - alias string - args map[string]*argument - bind any + name string + alias string + args map[string]*argument + bind any + multiple bool prev *Selection @@ -52,8 +53,14 @@ func (s *Selection) SelectWithAlias(alias, name string) *Selection { return sel } -func (s *Selection) Select(name ...string) *Selection { - return s.SelectWithAlias("", strings.Join(name, " ")) +func (s *Selection) Select(name string) *Selection { + return s.SelectWithAlias("", name) +} + +func (s *Selection) SelectMultiple(name ...string) *Selection { + sel := s.SelectWithAlias("", strings.Join(name, " ")) + sel.multiple = true + return sel } func (s *Selection) Arg(name string, value any) *Selection { @@ -70,13 +77,6 @@ func (s *Selection) Arg(name string, value any) *Selection { func (s *Selection) Bind(v interface{}) *Selection { sel := *s - // When there's multiple fields, bind the parent. - if strings.Contains(sel.name, " ") { - prev := *s.prev - prev.bind = v - sel.prev = &prev - return &sel - } sel.bind = v return &sel } @@ -104,17 +104,12 @@ func (s *Selection) Build(ctx context.Context) (string, error) { b.WriteString("query") path := s.path() - multiple := false for _, sel := range path { - if multiple { + if sel.prev != nil && sel.prev.multiple { return "", fmt.Errorf("sibling selections not end of chain") } - if strings.Contains(sel.name, " ") { - multiple = true - } - b.WriteRune('{') if sel.alias != "" { @@ -151,12 +146,10 @@ func (s *Selection) unpack(data any) error { k = i.alias } - // Try to assert type of the value - switch f := data.(type) { - case map[string]any: - data = f[k] - default: - data = f + if !i.multiple { + if f, ok := data.(map[string]any); ok { + data = f[k] + } } if i.bind != nil { diff --git a/internal/telemetry/attrs.go b/internal/telemetry/attrs.go index 36d2fa20..f830f3fc 100644 --- a/internal/telemetry/attrs.go +++ b/internal/telemetry/attrs.go @@ -75,9 +75,6 @@ const ( // Indicates the units for the progress numbers. ProgressUnitsAttr = "dagger.io/progress.units" - // The client ID that generated this telemetry. - ClientIDAttr = "dagger.io/client.id" - // The stdio stream a log corresponds to (1 for stdout, 2 for stderr). StdioStreamAttr = "stdio.stream" diff --git a/internal/telemetry/env.go b/internal/telemetry/env.go index 82c3528c..3c1b23ff 100644 --- a/internal/telemetry/env.go +++ b/internal/telemetry/env.go @@ -5,13 +5,12 @@ import ( "os" "strings" - "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) func PropagationEnv(ctx context.Context) []string { carrier := NewEnvCarrier(false) - otel.GetTextMapPropagator().Inject(ctx, carrier) + Propagator.Inject(ctx, carrier) return carrier.Env } diff --git a/internal/telemetry/init.go b/internal/telemetry/init.go index 66d2e6d0..724b5dd3 100644 --- a/internal/telemetry/init.go +++ b/internal/telemetry/init.go @@ -73,8 +73,16 @@ func ConfiguredSpanExporter(ctx context.Context) (sdktrace.SpanExporter, bool) { switch proto { case "http/protobuf", "http": + headers := map[string]string{} + if hs := os.Getenv("OTEL_EXPORTER_OTLP_HEADERS"); hs != "" { + for _, header := range strings.Split(hs, ",") { + name, value, _ := strings.Cut(header, "=") + headers[name] = value + } + } configuredSpanExporter, err = otlptracehttp.New(ctx, - otlptracehttp.WithEndpointURL(endpoint)) + otlptracehttp.WithEndpointURL(endpoint), + otlptracehttp.WithHeaders(headers)) case "grpc": var u *url.URL u, err = url.Parse(endpoint) @@ -180,9 +188,9 @@ func ConfiguredLogExporter(ctx context.Context) (sdklog.Exporter, bool) { return configuredLogExporter, configuredLogExporter != nil } -// FallbackResource is the fallback resource definition. A more specific +// fallbackResource is the fallback resource definition. A more specific // resource should be set in Init. -func FallbackResource() *resource.Resource { +func fallbackResource() *resource.Resource { return resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("dagger"), @@ -192,7 +200,6 @@ func FallbackResource() *resource.Resource { var ( // set by Init, closed by Close tracerProvider *sdktrace.TracerProvider = sdktrace.NewTracerProvider() - loggerProvider *sdklog.LoggerProvider = sdklog.NewLoggerProvider() ) type Config struct { @@ -230,6 +237,7 @@ const NearlyImmediate = 100 * time.Millisecond // sent live span telemetry. var LiveTracesEnabled = os.Getenv("OTEL_EXPORTER_OTLP_TRACES_LIVE") != "" +var Resource *resource.Resource var SpanProcessors = []sdktrace.SpanProcessor{} var LogProcessors = []sdklog.Processor{} @@ -247,19 +255,29 @@ func InitEmbedded(ctx context.Context, res *resource.Resource) context.Context { return Init(ctx, traceCfg) } +// Propagator is a composite propagator of everything we could possibly want. +// +// Do not rely on otel.GetTextMapPropagator() - it's prone to change from a +// random import. +var Propagator = propagation.NewCompositeTextMapPropagator( + propagation.Baggage{}, + propagation.TraceContext{}, +) + +// closeCtx holds on to the initial context returned by Init. Close will +// extract its providers and close them. +var closeCtx context.Context + // Init sets up the global OpenTelemetry providers tracing, logging, and // someday metrics providers. It is called by the CLI, the engine, and the // container shim, so it needs to be versatile. func Init(ctx context.Context, cfg Config) context.Context { // Set up a text map propagator so that things, well, propagate. The default // is a noop. - otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( - propagation.TraceContext{}, - propagation.Baggage{}, - )) + otel.SetTextMapPropagator(Propagator) // Inherit trace context from env if present. - ctx = otel.GetTextMapPropagator().Extract(ctx, NewEnvCarrier(true)) + ctx = Propagator.Extract(ctx, NewEnvCarrier(true)) // Log to slog. otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { @@ -267,9 +285,13 @@ func Init(ctx context.Context, cfg Config) context.Context { })) if cfg.Resource == nil { - cfg.Resource = FallbackResource() + cfg.Resource = fallbackResource() } + // Set up the global resource so we can pass it into dynamically allocated + // log/trace providers at runtime. + Resource = cfg.Resource + if cfg.Detect { if exp, ok := ConfiguredSpanExporter(ctx); ok { if LiveTracesEnabled { @@ -319,50 +341,35 @@ func Init(ctx context.Context, cfg Config) context.Context { // Set up a log provider if configured. if len(cfg.LiveLogExporters) > 0 { - logOpts := []sdklog.LoggerProviderOption{} + logOpts := []sdklog.LoggerProviderOption{ + sdklog.WithResource(cfg.Resource), + } for _, exp := range cfg.LiveLogExporters { processor := sdklog.NewBatchProcessor(exp, sdklog.WithExportInterval(NearlyImmediate)) LogProcessors = append(LogProcessors, processor) logOpts = append(logOpts, sdklog.WithProcessor(processor)) } - loggerProvider = sdklog.NewLoggerProvider(logOpts...) - - // TODO: someday do the following (once it exists) - // Register our TracerProvider as the global so any imported - // instrumentation in the future will default to using it. - // otel.SetLoggerProvider(loggerProvider) + ctx = WithLoggerProvider(ctx, sdklog.NewLoggerProvider(logOpts...)) } - return ctx -} + closeCtx = ctx -// Flush drains telemetry data, and is typically called just before a client -// goes away. -// -// NB: now that we wait for all spans to complete, this is less necessary, but -// it seems wise to keep it anyway, as the spots where it are needed are hard -// to find. -func Flush(ctx context.Context) { - if tracerProvider != nil { - if err := tracerProvider.ForceFlush(ctx); err != nil { - slog.Error("failed to flush spans", "error", err) - } - } + return ctx } // Close shuts down the global OpenTelemetry providers, flushing any remaining // data to the configured exporters. func Close() { - flushCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx := closeCtx + flushCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) defer cancel() - Flush(flushCtx) if tracerProvider != nil { if err := tracerProvider.Shutdown(flushCtx); err != nil { slog.Error("failed to shut down tracer provider", "error", err) } } - if loggerProvider != nil { + if loggerProvider := LoggerProvider(ctx); loggerProvider != nil { if err := loggerProvider.Shutdown(flushCtx); err != nil { slog.Error("failed to shut down logger provider", "error", err) } diff --git a/internal/telemetry/logging.go b/internal/telemetry/logging.go index 29fbd423..a152a042 100644 --- a/internal/telemetry/logging.go +++ b/internal/telemetry/logging.go @@ -7,11 +7,28 @@ import ( "time" "go.opentelemetry.io/otel/log" + sdklog "go.opentelemetry.io/otel/sdk/log" ) +type loggerProviderKey struct{} + +// WithLoggerProvider returns a new context with the given LoggerProvider. +func WithLoggerProvider(ctx context.Context, provider *sdklog.LoggerProvider) context.Context { + return context.WithValue(ctx, loggerProviderKey{}, provider) +} + +// LoggerProvider returns the LoggerProvider from the context. +func LoggerProvider(ctx context.Context) *sdklog.LoggerProvider { + loggerProvider := sdklog.NewLoggerProvider() + if val := ctx.Value(loggerProviderKey{}); val != nil { + loggerProvider = val.(*sdklog.LoggerProvider) + } + return loggerProvider +} + // Logger returns a logger with the given name. -func Logger(name string) log.Logger { - return loggerProvider.Logger(name) // TODO more instrumentation attrs +func Logger(ctx context.Context, name string) log.Logger { + return LoggerProvider(ctx).Logger(name) // TODO more instrumentation attrs } // SpanStdio returns a pair of io.WriteClosers which will send log records with @@ -23,9 +40,9 @@ func Logger(name string) log.Logger { // stdout/stderr and terminates them with an EOF, to confirm that all data has // been received. It should not be used for general-purpose logging. // -// Both streamsm must be closed to ensure that draining completes. +// Both streams must be closed to ensure that draining completes. func SpanStdio(ctx context.Context, name string, attrs ...log.KeyValue) SpanStreams { - logger := Logger(name) + logger := Logger(ctx, name) return SpanStreams{ Stdout: &spanStream{ Writer: &Writer{ @@ -56,7 +73,7 @@ type Writer struct { func NewWriter(ctx context.Context, name string, attrs ...log.KeyValue) io.Writer { return &Writer{ ctx: ctx, - logger: Logger(name), + logger: Logger(ctx, name), attrs: attrs, } } diff --git a/internal/telemetry/span.go b/internal/telemetry/span.go index 35c7cadf..cfeb85b4 100644 --- a/internal/telemetry/span.go +++ b/internal/telemetry/span.go @@ -1,6 +1,8 @@ package telemetry import ( + "context" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" @@ -30,6 +32,12 @@ func Passthrough() trace.SpanStartOption { return trace.WithAttributes(attribute.Bool(UIPassthroughAttr, true)) } +// Tracer returns a Tracer for the given library using the provider from +// the current span. +func Tracer(ctx context.Context, lib string) trace.Tracer { + return trace.SpanFromContext(ctx).TracerProvider().Tracer(lib) +} + // End is a helper to end a span with an error if the function returns an error. // // It is optimized for use as a defer one-liner with a function that has a diff --git a/internal/telemetry/transform.go b/internal/telemetry/transform.go index 99c86c18..cfca401e 100644 --- a/internal/telemetry/transform.go +++ b/internal/telemetry/transform.go @@ -48,7 +48,7 @@ func LogsToPB(sdl []sdklog.Record) []*otlplogsv1.ResourceLogs { if !iOk { // Either the resource or instrumentation scope were unknown. scopeLog = &otlplogsv1.ScopeLogs{ - Scope: InstrumentationScope(sd.InstrumentationScope()), + Scope: InstrumentationScopeToPB(sd.InstrumentationScope()), LogRecords: []*otlplogsv1.LogRecord{}, SchemaUrl: sd.InstrumentationScope().SchemaURL, } @@ -61,7 +61,7 @@ func LogsToPB(sdl []sdklog.Record) []*otlplogsv1.ResourceLogs { resources++ // The resource was unknown. rs = &otlplogsv1.ResourceLogs{ - Resource: Resource(res), + Resource: ResourceToPB(res), ScopeLogs: []*otlplogsv1.ScopeLogs{scopeLog}, SchemaUrl: res.SchemaURL(), } @@ -87,7 +87,7 @@ func LogsToPB(sdl []sdklog.Record) []*otlplogsv1.ResourceLogs { return rss } -func InstrumentationScope(il instrumentation.Scope) *otlpcommonv1.InstrumentationScope { +func InstrumentationScopeToPB(il instrumentation.Scope) *otlpcommonv1.InstrumentationScope { if il == (instrumentation.Scope{}) { return nil } @@ -103,7 +103,7 @@ func logRecord(l sdklog.Record) *otlplogsv1.LogRecord { l.WalkAttributes(func(kv log.KeyValue) bool { attrs = append(attrs, &otlpcommonv1.KeyValue{ Key: kv.Key, - Value: logValueToPB(kv.Value), + Value: LogValueToPB(kv.Value), }) return true }) @@ -113,7 +113,7 @@ func logRecord(l sdklog.Record) *otlplogsv1.LogRecord { TimeUnixNano: uint64(l.Timestamp().UnixNano()), SeverityNumber: otlplogsv1.SeverityNumber(l.Severity()), SeverityText: l.SeverityText(), - Body: logValueToPB(l.Body()), + Body: LogValueToPB(l.Body()), Attributes: attrs, // DroppedAttributesCount: 0, // Flags: 0, @@ -124,13 +124,22 @@ func logRecord(l sdklog.Record) *otlplogsv1.LogRecord { return s } -// Resource transforms a Resource into an OTLP Resource. -func Resource(r resource.Resource) *otlpresourcev1.Resource { +// ResourceToPB transforms a Resource into an OTLP Resource. +func ResourceToPB(r resource.Resource) *otlpresourcev1.Resource { return &otlpresourcev1.Resource{Attributes: resourceAttributes(r)} } -// Resource transforms a Resource into an OTLP Resource. -func ResourcePtr(r *resource.Resource) *otlpresourcev1.Resource { +// ResourceFromPB creates a *resource.Resource from a schema URL and +// protobuf encoded attributes. +func ResourceFromPB(schemaURL string, pb *otlpresourcev1.Resource) *resource.Resource { + if schemaURL == "" { + return resource.NewSchemaless(AttributesFromProto(pb.Attributes)...) + } + return resource.NewWithAttributes(schemaURL, AttributesFromProto(pb.Attributes)...) +} + +// ResourcePtrToPB transforms a *Resource into an OTLP Resource. +func ResourcePtrToPB(r *resource.Resource) *otlpresourcev1.Resource { if r == nil { return nil } @@ -325,7 +334,7 @@ func SpansToPB(sdl []sdktrace.ReadOnlySpan) []*otlptracev1.ResourceSpans { if !iOk { // Either the resource or instrumentation scope were unknown. scopeSpan = &otlptracev1.ScopeSpans{ - Scope: InstrumentationScope(sd.InstrumentationScope()), + Scope: InstrumentationScopeToPB(sd.InstrumentationScope()), Spans: []*otlptracev1.Span{}, SchemaUrl: sd.InstrumentationScope().SchemaURL, } @@ -338,7 +347,7 @@ func SpansToPB(sdl []sdktrace.ReadOnlySpan) []*otlptracev1.ResourceSpans { resources++ // The resource was unknown. rs = &otlptracev1.ResourceSpans{ - Resource: ResourcePtr(sd.Resource()), + Resource: ResourcePtrToPB(sd.Resource()), ScopeSpans: []*otlptracev1.ScopeSpans{scopeSpan}, SchemaUrl: sd.Resource().SchemaURL(), } @@ -380,11 +389,11 @@ func spanToPB(sd sdktrace.ReadOnlySpan) *otlptracev1.Span { Status: status(sd.Status().Code, sd.Status().Description), StartTimeUnixNano: uint64(sd.StartTime().UnixNano()), EndTimeUnixNano: uint64(sd.EndTime().UnixNano()), - Links: linksToPB(sd.Links()), + Links: SpanLinksToPB(sd.Links()), Kind: spanKindToPB(sd.SpanKind()), Name: sd.Name(), Attributes: KeyValues(sd.Attributes()), - Events: spanEventsToPB(sd.Events()), + Events: SpanEventsToPB(sd.Events()), DroppedAttributesCount: uint32(sd.DroppedAttributes()), DroppedEventsCount: uint32(sd.DroppedEvents()), DroppedLinksCount: uint32(sd.DroppedLinks()), @@ -429,7 +438,7 @@ func KeyValues(attrs []attribute.KeyValue) []*otlpcommonv1.KeyValue { } // linksFromPB transforms span Links to OTLP span linksFromPB. -func linksToPB(links []sdktrace.Link) []*otlptracev1.Span_Link { +func SpanLinksToPB(links []sdktrace.Link) []*otlptracev1.Span_Link { if len(links) == 0 { return nil } @@ -465,8 +474,8 @@ func buildSpanFlags(sc trace.SpanContext) uint32 { return uint32(flags) } -// spanEventsToPB transforms span Events to an OTLP span events. -func spanEventsToPB(es []sdktrace.Event) []*otlptracev1.Span_Event { +// SpanEventsToPB transforms span Events to an OTLP span events. +func SpanEventsToPB(es []sdktrace.Event) []*otlptracev1.Span_Event { if len(es) == 0 { return nil } @@ -563,22 +572,22 @@ func (s *readOnlySpan) Attributes() []attribute.KeyValue { } func (s *readOnlySpan) Links() []sdktrace.Link { - return linksFromPB(s.pb.Links) + return SpanLinksFromPB(s.pb.Links) } func (s *readOnlySpan) Events() []sdktrace.Event { - return spanEventsFromPB(s.pb.Events) + return SpanEventsFromPB(s.pb.Events) } func (s *readOnlySpan) Status() sdktrace.Status { return sdktrace.Status{ - Code: statusCode(s.pb.Status), + Code: StatusCodeFromPB(s.pb.Status), Description: s.pb.Status.GetMessage(), } } func (s *readOnlySpan) InstrumentationScope() instrumentation.Scope { - return instrumentationScope(s.is) + return InstrumentationScopeFromPB(s.is) } // Deprecated: use InstrumentationScope. @@ -588,13 +597,7 @@ func (s *readOnlySpan) InstrumentationLibrary() instrumentation.Library { // Resource returns information about the entity that produced the span. func (s *readOnlySpan) Resource() *resource.Resource { - if s.resource == nil { - return nil - } - if s.schemaURL != "" { - return resource.NewWithAttributes(s.schemaURL, AttributesFromProto(s.resource.Attributes)...) - } - return resource.NewSchemaless(AttributesFromProto(s.resource.Attributes)...) + return ResourceFromPB(s.schemaURL, s.resource) } // DroppedAttributes returns the number of attributes dropped by the span @@ -624,7 +627,7 @@ func (s *readOnlySpan) ChildSpanCount() int { var _ sdktrace.ReadOnlySpan = &readOnlySpan{} // status transform a OTLP span status into span code. -func statusCode(st *otlptracev1.Status) codes.Code { +func StatusCodeFromPB(st *otlptracev1.Status) codes.Code { if st == nil { return codes.Unset } @@ -636,8 +639,8 @@ func statusCode(st *otlptracev1.Status) codes.Code { } } -// linksFromPB transforms OTLP span links to span Links. -func linksFromPB(links []*otlptracev1.Span_Link) []sdktrace.Link { +// SpanLinksFromPB transforms OTLP span links to span Links. +func SpanLinksFromPB(links []*otlptracev1.Span_Link) []sdktrace.Link { if len(links) == 0 { return nil } @@ -669,8 +672,8 @@ func linksFromPB(links []*otlptracev1.Span_Link) []sdktrace.Link { return sl } -// spanEventsFromPB transforms OTLP span events to span Events. -func spanEventsFromPB(es []*otlptracev1.Span_Event) []sdktrace.Event { +// SpanEventsFromPB transforms OTLP span events to span Events. +func SpanEventsFromPB(es []*otlptracev1.Span_Event) []sdktrace.Event { if len(es) == 0 { return nil } @@ -814,7 +817,7 @@ func anyArrayToAttrValue(anyVals []*otlpcommonv1.AnyValue) attribute.Value { } } -func instrumentationScope(is *otlpcommonv1.InstrumentationScope) instrumentation.Scope { +func InstrumentationScopeFromPB(is *otlpcommonv1.InstrumentationScope) instrumentation.Scope { if is == nil { return instrumentation.Scope{} } @@ -833,11 +836,11 @@ func LogsFromPB(resLogs []*otlplogsv1.ResourceLogs) []sdklog.Record { logRec.SetTraceID(trace.TraceID(rec.GetTraceId())) logRec.SetSpanID(trace.SpanID(rec.GetSpanId())) logRec.SetTimestamp(time.Unix(0, int64(rec.GetTimeUnixNano()))) - logRec.SetBody(logValueFromPB(rec.GetBody())) + logRec.SetBody(LogValueFromPB(rec.GetBody())) logRec.SetSeverity(log.Severity(rec.GetSeverityNumber())) logRec.SetSeverityText(rec.GetSeverityText()) logRec.SetObservedTimestamp(time.Unix(0, int64(rec.GetObservedTimeUnixNano()))) - logRec.SetAttributes(logKVs(rec.GetAttributes())...) + logRec.SetAttributes(LogKeyValuesFromPB(rec.GetAttributes())...) logs = append(logs, logRec) } } @@ -845,7 +848,7 @@ func LogsFromPB(resLogs []*otlplogsv1.ResourceLogs) []sdklog.Record { return logs } -func logKVs(kvs []*otlpcommonv1.KeyValue) []log.KeyValue { +func LogKeyValuesFromPB(kvs []*otlpcommonv1.KeyValue) []log.KeyValue { res := make([]log.KeyValue, len(kvs)) for i, kv := range kvs { res[i] = logKeyValue(kv) @@ -856,7 +859,7 @@ func logKVs(kvs []*otlpcommonv1.KeyValue) []log.KeyValue { func logKeyValue(v *otlpcommonv1.KeyValue) log.KeyValue { return log.KeyValue{ Key: v.GetKey(), - Value: logValueFromPB(v.GetValue()), + Value: LogValueFromPB(v.GetValue()), } } @@ -880,7 +883,7 @@ func attrValue(v *otlpcommonv1.AnyValue) attribute.Value { } } -func logValueFromPB(v *otlpcommonv1.AnyValue) log.Value { +func LogValueFromPB(v *otlpcommonv1.AnyValue) log.Value { switch x := v.Value.(type) { case *otlpcommonv1.AnyValue_StringValue: return log.StringValue(v.GetStringValue()) @@ -899,7 +902,7 @@ func logValueFromPB(v *otlpcommonv1.AnyValue) log.Value { case *otlpcommonv1.AnyValue_ArrayValue: vals := make([]log.Value, 0, len(x.ArrayValue.GetValues())) for _, v := range x.ArrayValue.GetValues() { - vals = append(vals, logValueFromPB(v)) + vals = append(vals, LogValueFromPB(v)) } return log.SliceValue(vals...) case *otlpcommonv1.AnyValue_BytesValue: @@ -911,7 +914,7 @@ func logValueFromPB(v *otlpcommonv1.AnyValue) log.Value { } // Value transforms an attribute Value into an OTLP AnyValue. -func logValueToPB(v log.Value) *otlpcommonv1.AnyValue { +func LogValueToPB(v log.Value) *otlpcommonv1.AnyValue { av := new(otlpcommonv1.AnyValue) switch v.Kind() { case log.KindBool: @@ -933,7 +936,7 @@ func logValueToPB(v log.Value) *otlpcommonv1.AnyValue { case log.KindSlice: array := &otlpcommonv1.ArrayValue{} for _, e := range v.AsSlice() { - array.Values = append(array.Values, logValueToPB(e)) + array.Values = append(array.Values, LogValueToPB(e)) } av.Value = &otlpcommonv1.AnyValue_ArrayValue{ ArrayValue: array, @@ -943,7 +946,7 @@ func logValueToPB(v log.Value) *otlpcommonv1.AnyValue { for _, e := range v.AsMap() { kvList.Values = append(kvList.Values, &otlpcommonv1.KeyValue{ Key: e.Key, - Value: logValueToPB(e.Value), + Value: LogValueToPB(e.Value), }) } av.Value = &otlpcommonv1.AnyValue_KvlistValue{ diff --git a/main.go b/main.go index ef822728..1a8a7677 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/goharbor/harbor-cli/internal/dagger" "log" - "strings" ) const ( @@ -13,72 +12,61 @@ const ( 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" ) type HarborCli struct{} -func (m *HarborCli) Echo(stringArg string) string { - return stringArg -} - -// Returns a container that echoes whatever string argument is provided -func (m *HarborCli) ContainerEcho(stringArg string) *dagger.Container { - return dag.Container().From("alpine:latest").WithExec([]string{"echo", stringArg}) - -} - -// Returns lines that match a pattern in the files of the provided Directory -func (m *HarborCli) GrepDir(ctx context.Context, directoryArg *dagger.Directory, pattern string) (string, error) { - return dag.Container(). - From("alpine:latest"). - WithMountedDirectory("/mnt", directoryArg). - WithWorkdir("/mnt"). - WithExec([]string{"grep", "-R", pattern, "."}). - Stdout(ctx) - -} +func (m *HarborCli) Build( + ctx context.Context, + // +optional + // +defaultPath="./" + source *dagger.Directory) *dagger.Directory { -func (m *HarborCli) LintCode(ctx context.Context, directoryArg *dagger.Directory) *dagger.Container { - fmt.Println("👀 Running linter with Dagger...") - return dag.Container(). - From("golangci/golangci-lint:v1.59.1-alpine"). - WithMountedDirectory("/src", directoryArg). - WithWorkdir("/src"). - WithExec([]string{"golangci-lint", "run", "--timeout", "5m"}) - -} - -func (m *HarborCli) BuildHarbor(ctx context.Context, directoryArg *dagger.Directory) *dagger.Directory { fmt.Println("🛠️ Building with Dagger...") oses := []string{"linux", "darwin", "windows"} arches := []string{"amd64", "arm64"} outputs := dag.Directory() - golangcont := dag.Container(). - From("golang:latest"). - 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, _ := golangcont.File("main_go_path.txt").Contents(ctx) - trimmedPath := strings.TrimPrefix(main_go_txt_file, "./") - result := "/src/" + trimmedPath - main_go_path := strings.TrimRight(result, "\n") - 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) Lint( + ctx context.Context, + // +optional + // +defaultPath="./" + source *dagger.Directory, +) *dagger.Container { + fmt.Println("👀 Running linter with Dagger...") + return dag.Container(). + From("golangci/golangci-lint:v1.59.1-alpine"). + 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"). + WithMountedDirectory("/src", source). + WithWorkdir("/src"). + WithExec([]string{"golangci-lint", "run", "--timeout", "5m"}) + +} + func (m *HarborCli) PullRequest(ctx context.Context, directoryArg *dagger.Directory, githubToken string) { goreleaser := goreleaserContainer(directoryArg, githubToken).WithExec([]string{"release", "--snapshot", "--clean"}) _, err := goreleaser.Stderr(ctx) @@ -90,7 +78,7 @@ func (m *HarborCli) PullRequest(ctx context.Context, directoryArg *dagger.Direct } func (m *HarborCli) Release(ctx context.Context, directoryArg *dagger.Directory, githubToken string) { - goreleaser := goreleaserContainer(directoryArg, githubToken).WithExec([]string{"--clean"}) + goreleaser := goreleaserContainer(directoryArg, githubToken).WithExec([]string{"release", "--clean"}) _, err := goreleaser.Stderr(ctx) if err != nil { log.Printf("Error occured during release: %s", err) @@ -99,6 +87,36 @@ func (m *HarborCli) Release(ctx context.Context, directoryArg *dagger.Directory, log.Println("Release tasks completed successfully 🎉") } +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("/")). + WithEntrypoint([]string{"./harbor"}) + + 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}) + if err != nil { + panic(err) + } + fmt.Printf("Published to %s 🎉\n", addr) + return addr +} + func goreleaserContainer(directoryArg *dagger.Directory, githubToken string) *dagger.Container { token := dag.SetSecret("github_token", githubToken) @@ -112,5 +130,4 @@ func goreleaserContainer(directoryArg *dagger.Directory, githubToken string) *da WithMountedDirectory("/src", directoryArg).WithWorkdir("/src"). WithEnvVariable("TINI_SUBREAPER", "true"). WithSecretVariable("GITHUB_TOKEN", token) - } diff --git a/pkg/utils/helper.go b/pkg/utils/helper.go index 8484ce7d..612f814f 100644 --- a/pkg/utils/helper.go +++ b/pkg/utils/helper.go @@ -35,3 +35,8 @@ func FormatUrl(url string) string { } return url } + +func FormatSize(size int64) string { + mbSize := float64(size) / (1024 * 1024) + return fmt.Sprintf("%.2fMiB", mbSize) +} diff --git a/pkg/views/artifact/list/view.go b/pkg/views/artifact/list/view.go new file mode 100644 index 00000000..3b851aee --- /dev/null +++ b/pkg/views/artifact/list/view.go @@ -0,0 +1,48 @@ +package list + +import ( + "fmt" + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/base/tablelist" + "os" + "strconv" +) + +var columns = []table.Column{ + {Title: "ID", Width: 6}, + {Title: "Artifact Digest", Width: 20}, + {Title: "Type", Width: 12}, + {Title: "Size", Width: 12}, + {Title: "Vulnerabilities", Width: 15}, + {Title: "Push Time", Width: 12}, +} + +func ListArtifacts(artifacts []*models.Artifact) { + var rows []table.Row + for _, artifact := range artifacts { + pushTime, _ := utils.FormatCreatedTime(artifact.PushTime.String()) + artifactSize := utils.FormatSize(artifact.Size) + var totalVulnerabilities int64 + for _, scan := range artifact.ScanOverview { + totalVulnerabilities += scan.Summary.Total + } + rows = append(rows, table.Row{ + strconv.FormatInt(int64(artifact.ID), 10), + artifact.Digest[:16], + artifact.Type, + artifactSize, + strconv.FormatInt(totalVulnerabilities, 10), + pushTime, + }) + } + + m := tablelist.NewModel(columns, rows, len(rows)) + + if _, err := tea.NewProgram(m).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} diff --git a/pkg/views/confirmation.go b/pkg/views/confirmation.go new file mode 100644 index 00000000..8bbf7067 --- /dev/null +++ b/pkg/views/confirmation.go @@ -0,0 +1,21 @@ +package views + +import ( + "github.com/charmbracelet/huh" + log "github.com/sirupsen/logrus" +) + +func ConfirmElevation() (bool, error) { + var confirm bool + + err := huh.NewConfirm(). + Title("Are you sure to elevate the user to admin role?"). + Affirmative("Yes"). + Negative("No"). + Value(&confirm).Run() + if err != nil { + log.Fatal(err) + } + + return confirm, nil +}