From 9f6af87ecccd024fb6652b4bfb313177cf293bc9 Mon Sep 17 00:00:00 2001 From: vmsharoshkin Date: Mon, 17 Aug 2026 12:24:55 +0300 Subject: [PATCH] feat: refactor and linter --- .github/workflows/ci.yml | 8 +- .golangci.yml | 54 +++++++++++ CLAUDE.md | 4 +- CONTRIBUTING.md | 6 +- Makefile | 14 +-- README.md | 15 +-- cmd/job.go | 142 +--------------------------- cmd/repo.go | 19 +--- cmd/run.go | 59 ------------ go.mod | 9 +- go.sum | 14 +-- internal/config/config.go | 6 +- internal/output/output.go | 8 +- internal/repoapi/client.go | 15 ++- internal/workflowapi/application.go | 6 +- internal/workflowapi/client.go | 15 ++- internal/workflowapi/job.go | 66 ------------- internal/workflowapi/run.go | 44 --------- skill/SKILL.md | 4 +- 19 files changed, 128 insertions(+), 380 deletions(-) create mode 100644 .golangci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1f9b31..cb983e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,11 +13,13 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version: "1.26.6" cache: true - - name: vet - run: make vet + - name: golangci-lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.12.2 - name: test run: make test diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..eb3509d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,54 @@ +version: "2" +formatters: + enable: + - golines # checks if code is formatted, and fixes long lines + - gofumpt # enforces a stricter format than 'gofmt', while being backwards compatible + settings: + golines: + max-len: 130 + reformat-tags: false +linters: + enable: + - copyloopvar # Detects variable copies in loops that could cause bugs + - exhaustive # Ensures enum switch statements are exhaustive + - gocyclo # Checks cyclomatic complexity of functions + - gosec # Inspects source code for security problems + - misspell # Finds commonly misspelled English words + #- mnd # Detects magic numbers (constants with no explanation) + - nakedret # Finds naked returns in functions longer than a few lines + - revive # A fast, configurable, extensible, flexible, and beautiful linter for Go + - staticcheck # The advanced Go linter (go vet on steroids) + - modernize # Suggests simplifications to Go code, using modern language and library features + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - testifylint # checks usage of github.com/stretchr/testify + - sloglint # ensure consistent code style when using log/slog + - nolintlint # reports ill-formed or insufficient nolint directives + - mirror # reports wrong mirror patterns of bytes/strings usage + - intrange # finds places where for loops could make use of an integer range + - gocritic # provides diagnostics that check for bugs, performance and style issues + - funcorder # checks the order of functions, methods, and constructors + - exptostd # detects functions from golang.org/x/exp/ that can be replaced by std functions + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - embeddedstructfieldcheck # checks embedded types in structs + - asciicheck # checks that your code does not contain non-ASCII identifiers + - prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + #- wrapcheck # checks that errors returned from external packages are wrapped + #- goconst # finds repeated strings that could be replaced by a constant + - testpackage # Linter that makes you use a separate _test package. + settings: + gocyclo: + min-complexity: 40 + exhaustive: + default-signifies-exhaustive: true + exclusions: + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - path: '_test\.go' + linters: + - goconst + - gosec \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 3642279..de08096 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,13 +30,13 @@ publishes it, and `eds wf app status` (or the lower-level `eds wf run`/ make build # build for current platform -> ./bin/eds make build-all # cross-compile darwin/linux x amd64/arm64 -> ./dist/ make test # go test ./... -make vet # go vet ./... +make lint make tidy # go mod tidy make clean # remove ./bin and ./dist ``` There are currently no `_test.go` files in the repo, so `make test` is a no-op -until tests are added. `make all` runs `vet test build` in sequence. +until tests are added. `make all` runs `lint test build` in sequence. Building directly without make: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3edb226..fb04bfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,13 +24,13 @@ Go 1.22+ is required. Target platforms are Linux + macOS only. Useful targets: ```bash -make vet # go vet ./... +make lint make test # go test ./... make build # current platform into ./bin/ make build-all # full matrix into ./dist/ ``` -Run `make vet` and `make test` before opening a PR — CI runs the same checks. +Run `make lint` and `make test` before opening a PR — CI runs the same checks. ## Code conventions @@ -54,7 +54,7 @@ In short: 1. Fork the repo and create a branch off `main`. 2. Make your change, with tests where it makes sense. -3. Run `make vet` and `make test`. +3. Run `make lint` and `make test`. 4. Open a pull request describing what changed and why. ## Reporting a security issue diff --git a/Makefile b/Makefile index 7443ee3..be62829 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,6 @@ # make build-one - helper invoked by build-all (GOOS=... GOARCH=...) # make clean - remove ./bin and ./dist # make test - run `go test ./...` -# make vet - run `go vet ./...` # make tidy - run `go mod tidy` # make install - `go install` into $GOBIN # make release - build-all + sha256 sums @@ -47,7 +46,7 @@ LDFLAGS := -s -w -X main.version=$(VERSION) # ---- targets -------------------------------------------------------------- .PHONY: all -all: vet test build +all: lint test build .PHONY: build build: @@ -155,10 +154,6 @@ install: test: go test $(GOFLAGS) ./... -.PHONY: vet -vet: - go vet ./... - .PHONY: tidy tidy: go mod tidy @@ -177,8 +172,13 @@ help: @echo " upload build-all + publish to S3 (BUCKET=... VERSION=...)" @echo " upload-latest only update the 'latest' pointer in the bucket" @echo " install go install into \$$GOBIN" - @echo " test, vet, tidy standard Go targets" + @echo " lint run golangci-lint" + @echo " test, tidy standard Go targets" @echo " clean remove ./bin and ./dist" openapi-generator: openapi-generator-cli generate -i openapi-public.yaml -g go -o ./internal/workflow_client -c .openapi-generator.yaml + +.PHONY: lint +lint: + golangci-lint run --fix diff --git a/README.md b/README.md index e21b5c4..b2a0f14 100644 --- a/README.md +++ b/README.md @@ -123,14 +123,9 @@ eds wf app deployments list publish history eds wf app status run status + live URL eds wf run show show a run's status, stages and jobs -eds wf run list [--pipeline-id ID] list runs eds wf run stop stop a running run -eds wf job show show job details -eds wf job list --run-id ID list jobs for a run -eds wf job logs stream a job's logs -eds wf job retry retry a failed/canceled job -eds wf job stop stop a running job +eds wf job logs get logs for a job ``` ### Login @@ -209,7 +204,7 @@ eds wf app status "$APP_ID" --json | jq -r '.application.run.stages[].jobs[] | s `eds wf run` and `eds wf job` are the lower-level primitives behind `eds wf app status` — use them directly when you need to inspect or control a -particular run/job (e.g. `eds wf run stop`, `eds wf job retry`). +particular run (e.g. `eds wf run stop`) or stream job logs (`eds wf job logs`). ## File upload / push @@ -258,7 +253,7 @@ curl -fsSL https://storage.cloud.ru/my-bucket/evolution-devservices-cli/install. ## Development ```bash -make vet # go vet ./... +make lint make test # go test ./... make build # current platform into ./bin/ make build-all # full matrix into ./dist/ @@ -279,8 +274,8 @@ cmd/ repo.go # `eds repo list|create|show|delete|clone` wf.go # `eds wf` parent command (groups app/run/job) app.go # `eds wf app create|list|show|update|delete|deploy|deployments|status` - run.go # `eds wf run show|list|stop` - job.go # `eds wf job show|list|logs|retry|stop` + run.go # `eds wf run show|stop` + job.go # `eds wf job logs` internal/ config/ # disk config + env overrides output/ # JSON / table formatting diff --git a/cmd/job.go b/cmd/job.go index dc13137..a0a057b 100644 --- a/cmd/job.go +++ b/cmd/job.go @@ -6,9 +6,6 @@ import ( workflowclient "github.com/cloud-ru/evolution-devservices-cli/internal/workflow_client" "github.com/spf13/cobra" - - "github.com/cloud-ru/evolution-devservices-cli/internal/output" - "github.com/cloud-ru/evolution-devservices-cli/internal/workflowapi" ) // newJobCmd creates the parent `eds wf job` command and all its subcommands. @@ -19,98 +16,9 @@ func newJobCmd() *cobra.Command { Use: "job", Short: "Inspect and control Workflow Studio jobs", } - cmd.AddCommand(newJobShowCmd()) - cmd.AddCommand(newJobListCmd()) - cmd.AddCommand(newJobLogsCmd()) - cmd.AddCommand(newJobRetryCmd()) - cmd.AddCommand(newJobStopCmd()) - return cmd -} - -func newJobShowCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "show ", - Short: "Show job details", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - ctx, err := resolveContext(cmd) - if err != nil { - return err - } - if err := ctx.ensureWorkflowAuth(cmd.Context()); err != nil { - return err - } - - job, err := ctx.WorkflowAPI.GetJob(cmd.Context(), args[0]) - if err != nil { - return err - } - - if ctx.Printer.Format == output.FormatJSON { - return ctx.Printer.PrintJSON(job) - } - ctx.Printer.KeyValue([][2]string{ - {"id", job.ID}, - {"name", job.Name}, - {"run_id", job.RunID}, - {"stage_id", job.StageID}, - {"type", job.Type}, - {"status", string(job.Status)}, - {"updated_at", output.HumanTime(job.UpdatedAt)}, - }) - return nil - }, - } - return cmd -} - -func newJobListCmd() *cobra.Command { - var ( - runID string - limit int - offset int - ) - - cmd := &cobra.Command{ - Use: "list", - Short: "List jobs for a run", - Example: ` eds wf job list --run-id my-run-id - eds wf job list --run-id my-run-id --json | jq '.[].status'`, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx, err := resolveContext(cmd) - if err != nil { - return err - } - if err := ctx.ensureWorkflowAuth(cmd.Context()); err != nil { - return err - } - - jobs, err := ctx.WorkflowAPI.ListJobs(cmd.Context(), workflowapi.ListJobsOptions{ - RunID: runID, - Limit: limit, - Offset: offset, - }) - if err != nil { - return err - } - - if ctx.Printer.Format == output.FormatJSON { - return ctx.Printer.PrintJSON(jobs) - } - headers := []string{"ID", "NAME", "STAGE_ID", "STATUS", "UPDATED"} - rows := make([][]string, 0, len(jobs)) - for _, j := range jobs { - rows = append(rows, []string{j.ID, j.Name, j.StageID, string(j.Status), output.HumanTime(j.UpdatedAt)}) - } - ctx.Printer.Table(headers, rows) - return nil - }, - } + cmd.AddCommand(newJobLogsCmd()) - cmd.Flags().StringVar(&runID, "run-id", "", "run id to list jobs for (required)") - cmd.Flags().IntVar(&limit, "limit", 50, "page size") - cmd.Flags().IntVar(&offset, "offset", 0, "offset") return cmd } @@ -157,51 +65,3 @@ func newJobLogsCmd() *cobra.Command { return cmd } - -func newJobRetryCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "retry ", - Short: "Retry a failed or canceled job", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - ctx, err := resolveContext(cmd) - if err != nil { - return err - } - if err := ctx.ensureWorkflowAuth(cmd.Context()); err != nil { - return err - } - - if err := ctx.WorkflowAPI.RetryJob(cmd.Context(), args[0]); err != nil { - return err - } - fmt.Fprintf(cmd.OutOrStdout(), "Retrying job %s\n", args[0]) - return nil - }, - } - return cmd -} - -func newJobStopCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "stop ", - Short: "Stop a running job", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - ctx, err := resolveContext(cmd) - if err != nil { - return err - } - if err := ctx.ensureWorkflowAuth(cmd.Context()); err != nil { - return err - } - - if err := ctx.WorkflowAPI.StopJob(cmd.Context(), args[0]); err != nil { - return err - } - fmt.Fprintf(cmd.OutOrStdout(), "Stopped job %s\n", args[0]) - return nil - }, - } - return cmd -} diff --git a/cmd/repo.go b/cmd/repo.go index 6cdaa44..5afdf7e 100644 --- a/cmd/repo.go +++ b/cmd/repo.go @@ -8,6 +8,7 @@ import ( "os/exec" "strings" + "github.com/google/uuid" "github.com/spf13/cobra" "github.com/cloud-ru/evolution-devservices-cli/internal/output" @@ -341,20 +342,6 @@ func resolveRepoID(ctx context.Context, r *runtimeContext, ref string) (string, } func looksLikeUUID(s string) bool { - if len(s) != 36 { - return false - } - for i, c := range s { - switch i { - case 8, 13, 18, 23: - if c != '-' { - return false - } - default: - if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { - return false - } - } - } - return true + _, err := uuid.Parse(s) + return err == nil } diff --git a/cmd/run.go b/cmd/run.go index 737a8eb..0e2a070 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -6,7 +6,6 @@ import ( "github.com/spf13/cobra" "github.com/cloud-ru/evolution-devservices-cli/internal/output" - "github.com/cloud-ru/evolution-devservices-cli/internal/workflowapi" ) // newRunCmd creates the parent `eds wf run` command and all its subcommands. @@ -18,7 +17,6 @@ func newRunCmd() *cobra.Command { Short: "Inspect and control Workflow Studio pipeline runs", } cmd.AddCommand(newRunShowCmd()) - cmd.AddCommand(newRunListCmd()) cmd.AddCommand(newRunStopCmd()) return cmd } @@ -68,63 +66,6 @@ func newRunShowCmd() *cobra.Command { return cmd } -func newRunListCmd() *cobra.Command { - var ( - pipelineID string - runType string - sort string - limit int - offset int - ) - - cmd := &cobra.Command{ - Use: "list", - Short: "List Workflow Studio pipeline runs", - Example: ` eds wf run list --pipeline-id my-pipeline-id - eds wf run list --type workflow --limit 20`, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx, err := resolveContext(cmd) - if err != nil { - return err - } - if err := ctx.ensureWorkflowAuth(cmd.Context()); err != nil { - return err - } - - resp, err := ctx.WorkflowAPI.ListRuns(cmd.Context(), workflowapi.ListRunsOptions{ - PipelineID: pipelineID, - Type: runType, - Sort: sort, - Limit: limit, - Offset: offset, - }) - if err != nil { - return err - } - - if ctx.Printer.Format == output.FormatJSON { - return ctx.Printer.PrintJSON(resp) - } - - headers := []string{"ID", "PIPELINE_ID", "BRANCH", "STATUS", "UPDATED"} - rows := make([][]string, 0, len(resp.Runs)) - for _, r := range resp.Runs { - rows = append(rows, []string{r.ID, r.PipelineID, r.Branch, string(r.Status), output.HumanTime(r.UpdatedAt)}) - } - ctx.Printer.Table(headers, rows) - fmt.Fprintf(cmd.OutOrStdout(), "Showing %d of %d\n", len(resp.Runs), resp.Total) - return nil - }, - } - - cmd.Flags().StringVar(&pipelineID, "pipeline-id", "", "filter by pipeline id") - cmd.Flags().StringVar(&runType, "type", "", "filter by pipeline type: cicd, workflow") - cmd.Flags().StringVar(&sort, "sort", "", "sort order") - cmd.Flags().IntVar(&limit, "limit", 50, "page size") - cmd.Flags().IntVar(&offset, "offset", 0, "offset") - return cmd -} - func newRunStopCmd() *cobra.Command { cmd := &cobra.Command{ Use: "stop ", diff --git a/go.mod b/go.mod index 474b808..37918cb 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,13 @@ module github.com/cloud-ru/evolution-devservices-cli -go 1.22 +go 1.26.6 -require github.com/spf13/cobra v1.8.0 +require ( + github.com/google/uuid v1.6.0 + github.com/spf13/cobra v1.10.2 +) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.9 // indirect ) diff --git a/go.sum b/go.sum index d0e8c2c..fd37884 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,12 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go index 0208f24..c605b4e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -106,7 +106,7 @@ func (c *Config) Save() error { return fmt.Errorf("create config dir: %w", err) } - data, err := json.MarshalIndent(c, "", " ") + data, err := json.MarshalIndent(c, "", " ") //nolint:gosec // config file intentionally stores API key if err != nil { return fmt.Errorf("marshal config: %w", err) } @@ -138,9 +138,9 @@ func configPath() (string, error) { } func expand(p string) string { - if strings.HasPrefix(p, "~") { + if after, ok := strings.CutPrefix(p, "~"); ok { if home, err := os.UserHomeDir(); err == nil { - return filepath.Join(home, strings.TrimPrefix(p, "~")) + return filepath.Join(home, after) } } return p diff --git a/internal/output/output.go b/internal/output/output.go index aa956df..ef0acbe 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -111,14 +111,14 @@ func (p *Printer) KeyValue(pairs [][2]string) { return } - max := 0 + maxLen := 0 for _, kv := range pairs { - if len(kv[0]) > max { - max = len(kv[0]) + if len(kv[0]) > maxLen { + maxLen = len(kv[0]) } } for _, kv := range pairs { - fmt.Fprintf(p.W, "%-*s %s\n", max, kv[0]+":", kv[1]) + fmt.Fprintf(p.W, "%-*s %s\n", maxLen, kv[0]+":", kv[1]) } } diff --git a/internal/repoapi/client.go b/internal/repoapi/client.go index 2eb44cd..4437dcd 100644 --- a/internal/repoapi/client.go +++ b/internal/repoapi/client.go @@ -108,7 +108,12 @@ func (c *Client) Do(ctx context.Context, method, path string, query url.Values, if out != nil && len(respBody) > 0 { if err := json.Unmarshal(respBody, out); err != nil { if requestID != "" { - return fmt.Errorf("decode response: %w (body: %s, request-id: %s)", err, truncate(string(respBody), 256), requestID) + return fmt.Errorf( + "decode response: %w (body: %s, request-id: %s)", + err, + truncate(string(respBody), 256), + requestID, + ) } return fmt.Errorf("decode response: %w (body: %s)", err, truncate(string(respBody), 256)) } @@ -163,7 +168,7 @@ func parseError(status int, body []byte, requestID string) error { msg = errResp.Error } if msg == "" && len(validResp.Errors) > 0 { - var parts []string + parts := make([]string, 0, len(validResp.Errors)) for field, errs := range validResp.Errors { parts = append(parts, fmt.Sprintf("%s: %s", field, strings.Join(errs, ", "))) } @@ -172,9 +177,9 @@ func parseError(status int, body []byte, requestID string) error { return &APIError{StatusCode: status, Message: msg, Body: string(body), RequestID: requestID} } -func truncate(s string, max int) string { - if len(s) <= max { +func truncate(s string, maxLen int) string { + if len(s) <= maxLen { return s } - return s[:max] + "…" + return s[:maxLen] + "…" } diff --git a/internal/workflowapi/application.go b/internal/workflowapi/application.go index 63c69c0..e40349b 100644 --- a/internal/workflowapi/application.go +++ b/internal/workflowapi/application.go @@ -181,7 +181,11 @@ func (c *Client) CreateDeployment(ctx context.Context, applicationID string) (*D // ListDeployments lists deployments for an application, most recent first // when Sort is SortCreatedAtDesc. -func (c *Client) ListDeployments(ctx context.Context, applicationID string, opts ListDeploymentsOptions) (*DeploymentListResponse, error) { +func (c *Client) ListDeployments( + ctx context.Context, + applicationID string, + opts ListDeploymentsOptions, +) (*DeploymentListResponse, error) { if c.projectID == "" { return nil, fmt.Errorf("project id is not configured; use --project or EDS_PROJECT_ID") } diff --git a/internal/workflowapi/client.go b/internal/workflowapi/client.go index 05b8e57..b233523 100644 --- a/internal/workflowapi/client.go +++ b/internal/workflowapi/client.go @@ -112,7 +112,12 @@ func (c *Client) Do(ctx context.Context, method, path string, query url.Values, if out != nil && len(respBody) > 0 { if err := json.Unmarshal(respBody, out); err != nil { if requestID != "" { - return fmt.Errorf("decode response: %w (body: %s, request-id: %s)", err, truncate(string(respBody), 256), requestID) + return fmt.Errorf( + "decode response: %w (body: %s, request-id: %s)", + err, + truncate(string(respBody), 256), + requestID, + ) } return fmt.Errorf("decode response: %w (body: %s)", err, truncate(string(respBody), 256)) } @@ -165,7 +170,7 @@ func parseError(status int, body []byte, requestID string) error { msg = errResp.Error } if msg == "" && len(validResp.Errors) > 0 { - var parts []string + parts := make([]string, 0, len(validResp.Errors)) for field, errs := range validResp.Errors { parts = append(parts, fmt.Sprintf("%s: %s", field, strings.Join(errs, ", "))) } @@ -174,9 +179,9 @@ func parseError(status int, body []byte, requestID string) error { return &APIError{StatusCode: status, Message: msg, Body: string(body), RequestID: requestID} } -func truncate(s string, max int) string { - if len(s) <= max { +func truncate(s string, maxLen int) string { + if len(s) <= maxLen { return s } - return s[:max] + "…" + return s[:maxLen] + "…" } diff --git a/internal/workflowapi/job.go b/internal/workflowapi/job.go index a5f3244..8aa5446 100644 --- a/internal/workflowapi/job.go +++ b/internal/workflowapi/job.go @@ -1,12 +1,5 @@ package workflowapi -import ( - "context" - "fmt" - "net/url" - "strconv" -) - // JobStatus is the lifecycle state of a job. type JobStatus string @@ -33,62 +26,3 @@ type Job struct { CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } - -// ListJobsOptions configures ListJobs. -type ListJobsOptions struct { - RunID string - Limit int - Offset int -} - -// GetJob fetches a single job by id. -func (c *Client) GetJob(ctx context.Context, id string) (*Job, error) { - if c.projectID == "" { - return nil, fmt.Errorf("project id is not configured; use --project or EDS_PROJECT_ID") - } - var out Job - if err := c.Do(ctx, "GET", fmt.Sprintf("/project/%s/job/%s", c.projectID, id), nil, nil, &out); err != nil { - return nil, err - } - return &out, nil -} - -// ListJobs lists jobs belonging to a run. -func (c *Client) ListJobs(ctx context.Context, opts ListJobsOptions) ([]Job, error) { - if c.projectID == "" { - return nil, fmt.Errorf("project id is not configured; use --project or EDS_PROJECT_ID") - } - if opts.RunID == "" { - return nil, fmt.Errorf("run id is required") - } - q := url.Values{} - q.Set("run_id", opts.RunID) - if opts.Limit > 0 { - q.Set("limit", strconv.Itoa(opts.Limit)) - } - if opts.Offset > 0 { - q.Set("offset", strconv.Itoa(opts.Offset)) - } - - var out []Job - if err := c.Do(ctx, "GET", fmt.Sprintf("/project/%s/job/list", c.projectID), q, nil, &out); err != nil { - return nil, err - } - return out, nil -} - -// RetryJob re-runs a failed or canceled job. -func (c *Client) RetryJob(ctx context.Context, id string) error { - if c.projectID == "" { - return fmt.Errorf("project id is not configured; use --project or EDS_PROJECT_ID") - } - return c.Do(ctx, "POST", fmt.Sprintf("/project/%s/job/%s/retry", c.projectID, id), nil, nil, nil) -} - -// StopJob cancels a running job. -func (c *Client) StopJob(ctx context.Context, id string) error { - if c.projectID == "" { - return fmt.Errorf("project id is not configured; use --project or EDS_PROJECT_ID") - } - return c.Do(ctx, "POST", fmt.Sprintf("/project/%s/job/%s/stop", c.projectID, id), nil, nil, nil) -} diff --git a/internal/workflowapi/run.go b/internal/workflowapi/run.go index eac244c..3c2f301 100644 --- a/internal/workflowapi/run.go +++ b/internal/workflowapi/run.go @@ -3,8 +3,6 @@ package workflowapi import ( "context" "fmt" - "net/url" - "strconv" ) // RunStatus is the lifecycle state of a pipeline run. @@ -47,16 +45,6 @@ type RunListResponse struct { Total int `json:"total"` } -// ListRunsOptions configures ListRuns. -type ListRunsOptions struct { - PipelineID string - Type string // "cicd" or "workflow" - Sort string - WithConfig bool - Limit int - Offset int -} - // GetRun fetches a single run by id, including its stages and jobs. func (c *Client) GetRun(ctx context.Context, id string) (*Run, error) { if c.projectID == "" { @@ -69,38 +57,6 @@ func (c *Client) GetRun(ctx context.Context, id string) (*Run, error) { return &out, nil } -// ListRuns lists runs in the configured project. -func (c *Client) ListRuns(ctx context.Context, opts ListRunsOptions) (*RunListResponse, error) { - if c.projectID == "" { - return nil, fmt.Errorf("project id is not configured; use --project or EDS_PROJECT_ID") - } - q := url.Values{} - if opts.PipelineID != "" { - q.Set("pipeline_id", opts.PipelineID) - } - if opts.Type != "" { - q.Set("type", opts.Type) - } - if opts.Sort != "" { - q.Set("sort", opts.Sort) - } - if opts.WithConfig { - q.Set("with_config", "true") - } - if opts.Limit > 0 { - q.Set("limit", strconv.Itoa(opts.Limit)) - } - if opts.Offset > 0 { - q.Set("offset", strconv.Itoa(opts.Offset)) - } - - var out RunListResponse - if err := c.Do(ctx, "GET", fmt.Sprintf("/project/%s/run/list", c.projectID), q, nil, &out); err != nil { - return nil, err - } - return &out, nil -} - // StopRun cancels a running run. func (c *Client) StopRun(ctx context.Context, id string) error { if c.projectID == "" { diff --git a/skill/SKILL.md b/skill/SKILL.md index 800fc05..f4af969 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -67,8 +67,8 @@ Errors go to stderr and the process exits non-zero. | `eds wf app deploy [--json]` | Run the pipeline and publish (the "deploy" action) | | `eds wf app deployments [--json]` | List publish history for an application | | `eds wf app status [--json]` | Convenience: run status + stage/job breakdown + live URL | -| `eds wf run show [--json]` / `eds wf run list [--pipeline-id ID] [--json]` / `eds wf run stop ` | Inspect/control a pipeline run | -| `eds wf job show [--json]` / `eds wf job list --run-id ID [--json]` / `eds wf job logs ` / `eds wf job retry\|stop ` | Inspect/control a job | +| `eds wf run show [--json]` / `eds wf run stop ` | Inspect/control a pipeline run | +| `eds wf job logs ` | Stream job logs | | `eds login --api-key --project [--repo-api-url URL]` | Persist credentials (one-time setup) | The `` argument on `eds repo *` and `--repository` on