diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..54897f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +name: CI + +# Run on every push and on pull requests targeting main. +on: + push: + branches: ['**'] + pull_request: + branches: [main] + +# Read-only access is sufficient for running tests and linting. +permissions: + contents: read + +jobs: + # Run the full test suite with race detection and generate a coverage report. + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + # Go version is read from go.mod so it stays in sync with the project. + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + # Verify dependency checksums match go.sum (detects corrupted or tampered modules). + - name: Verify dependencies + run: go mod verify + + - name: Run tests + run: go test -race -coverprofile=coverage.out -covermode=atomic ./... + + # Print coverage summary to the CI log. + - name: Coverage summary + run: go tool cover -func=coverage.out | tail -1 + + # Static analysis: go vet for built-in checks, golangci-lint for extended linting. + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Run go vet + run: go vet ./... + + # golangci-lint v2 runs 50+ linters in a single pass. + # Without a .golangci.yml config file it uses sensible defaults. + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + version: v2.10.1 + + # Verify the project compiles. Cross-platform builds are handled by + # GoReleaser at release time, so a single-platform check suffices here. + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Build + run: go build -o /dev/null . + + # Validate .goreleaser.yml so config errors are caught before tagging a release. + - name: Verify GoReleaser config + uses: goreleaser/goreleaser-action@v6 + with: + args: check diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml new file mode 100644 index 0000000..50dbdbd --- /dev/null +++ b/.github/workflows/govulncheck.yml @@ -0,0 +1,57 @@ +# Runs Go's official vulnerability scanner against project dependencies. +# Fails the job if any known vulnerabilities affect the code. +# See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck + +name: govulncheck + +on: + push: + branches: [main] + pull_request: + # Weekly scan catches new vulnerabilities even when the code hasn't changed. + schedule: + - cron: '0 9 * * 1' # Every Monday at 9:00 UTC + +permissions: + contents: read + issues: write + +jobs: + govulncheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + # golang/govulncheck-action runs govulncheck against all packages (./...). + # With the default text output format, the job fails if vulnerabilities are found. + - uses: golang/govulncheck-action@v1 + with: + go-version-file: go.mod + + # Open a GitHub issue when the scheduled scan finds vulnerabilities. + # Only runs on cron failures — PR and push failures are visible in the checks UI. + notify: + needs: govulncheck + runs-on: ubuntu-latest + if: failure() && github.event_name == 'schedule' + steps: + - uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'govulncheck: vulnerability detected', + body: [ + 'The weekly [govulncheck scan](' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + ') found vulnerabilities.', + '', + 'Review the workflow run and update affected dependencies.' + ].join('\n'), + labels: ['security'] + }); diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ddcfd96..d57156e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,39 +1,34 @@ -# This is a basic workflow to help you get started with Actions - name: Release with GoReleaser -# Controls when the action will run. +# Triggered by pushing a version tag (e.g. v1.2.3) or manually via the Actions tab. on: - # Triggers the release workflow only for new tags push: tags: - '*' - - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# A workflow run is made up of one or more jobs that can run sequentially or in parallel +# GoReleaser needs write access to create the GitHub release and upload artifacts. +permissions: + contents: write + jobs: - # This workflow contains a single job called "build" release: - # The type of runner that the job will run on runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + # Full history is required for GoReleaser to generate the changelog. - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - # Runs a single command using the runners shell + # Go version is read from go.mod so it stays in sync with the project. - name: Set up Go uses: actions/setup-go@v6 with: - go-version: 1.26 + go-version-file: go.mod - # Runs a set of commands using the runners shell + # GoReleaser builds, packages and publishes the release. + # See .goreleaser.yml for build targets and packaging configuration. - name: Run GoReleaser uses: goreleaser/goreleaser-action@v7 with: @@ -41,4 +36,3 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..06a7064 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,41 @@ +# Ensure PR titles follow the Conventional Commits format so that +# squash-merged commits produce a clean changelog via GoReleaser. +# See: https://www.conventionalcommits.org +name: PR Title + +on: + pull_request_target: + types: [opened, edited, synchronize, reopened] + +permissions: + pull-requests: read + +jobs: + lint: + name: Validate PR title + runs-on: ubuntu-latest + steps: + # Validates that the PR title matches the conventional commit format. + # Examples of valid titles: + # feat: add token refresh command + # fix(auth): handle expired tokens + # docs: update CONTRIBUTING.md + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Types allowed in PR titles. These map to GoReleaser changelog groups. + types: | + feat + fix + docs + chore + ci + test + refactor + perf + style + build + revert + # Require a scope in parentheses (optional — set to true to enforce). + requireScope: false diff --git a/.gitignore b/.gitignore index 1cf384b..5e01dcd 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,10 @@ Temporary Items *.yaml *.yml +# Allow test golden files and GitHub config +!**/testdata/** +!.github/** + dist/ /imscli go.mod.local diff --git a/.goreleaser.yml b/.goreleaser.yml index 8bfdc52..41c7072 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -22,12 +22,24 @@ checksum: name_template: 'checksums.txt' snapshot: version_template: "{{ .Version }}-next" +# Group commits by conventional-commit prefix for cleaner release notes. changelog: sort: asc + groups: + - title: Features + regexp: '^.*?feat(\([^)]+\))?!?:.+$' + order: 0 + - title: Bug Fixes + regexp: '^.*?fix(\([^)]+\))?!?:.+$' + order: 1 + - title: Other + order: 999 filters: exclude: - '^docs:' - '^test:' + - '^ci:' + - '^chore:' nfpms: - file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' homepage: "https://github.com/adobe/imscli" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ba3aff..1d8f1f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,91 @@ # Contributing -## Release process + +This project wraps [adobe/ims-go](https://github.com/adobe/ims-go). + +## Development + +```bash +# Build +go build -o imscli . + +# Run all tests +go test ./... + +# Run a single test by name +go test ./ims/ -run TestValidateURL + +# Vet (static analysis) +go vet ./... +``` + +## CI Pipelines + +All pipelines are defined in `.github/workflows/`. + +### CI (`ci.yml`) + +**Triggers:** Every push to any branch and pull requests targeting `main`. + +Runs three parallel jobs: + +- **Test** — Runs `go test -race` with coverage and prints a coverage summary to the log. Verifies dependency checksums with `go mod verify`. +- **Lint** — Runs `go vet` and [golangci-lint](https://golangci-lint.run/) for extended static analysis. +- **Build** — Verifies the project compiles and validates `.goreleaser.yml` with `goreleaser check`. Cross-platform builds are handled by GoReleaser at release time, so a single-platform check suffices here. + +### PR Title (`pr-title.yml`) + +**Triggers:** When a pull request is opened, edited, synchronized or reopened. + +Validates that the PR title follows the [Conventional Commits](https://www.conventionalcommits.org) format (e.g. `feat: add token refresh`, `fix(auth): handle expired tokens`). This is enforced because the repository is configured for **squash merging only** — the PR title becomes the commit message on `main`, and GoReleaser uses these prefixes to group the release changelog into Features, Bug Fixes, etc. + +### Release (`main.yml`) + +**Triggers:** Pushing a version tag (e.g. `v1.2.3`) or manual dispatch from the Actions tab. + +Runs [GoReleaser](https://goreleaser.com) to build cross-platform binaries (linux/darwin/windows × amd64/arm64), package them as archives and system packages (deb, rpm, apk), generate a changelog grouped by commit type, and publish a GitHub Release with all artifacts. + +### CodeQL (`codeql-analysis.yml`) + +**Triggers:** Every push, pull requests targeting `main`, and weekly on a cron schedule. + +Runs GitHub's CodeQL security analysis to detect vulnerabilities in the Go source code. + +### govulncheck (`govulncheck.yml`) + +**Triggers:** Every push to `main`, pull requests, and weekly on Monday at 9:00 UTC. + +Runs Go's official vulnerability scanner ([govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck)) against all packages. The job fails if any known vulnerabilities in the Go vulnerability database affect the code. Unlike general-purpose scanners, govulncheck traces call graphs — it only reports vulnerabilities in functions your code actually calls. The weekly schedule catches new vulnerabilities even when the code hasn't changed. If the scheduled scan fails, a GitHub issue labeled `security` is created automatically. + +## Repository Settings + +- **Squash merge only** — Merge commits and rebase merging are disabled. The PR title is used as the squash commit message, ensuring conventional commit messages land on `main`. +- **Auto-delete branches** — Head branches are automatically deleted after a PR is merged. +- **Renovate auto-merge** — [Renovate](https://docs.renovatebot.com/) monitors `go.mod` for dependency updates and opens PRs automatically. Patch updates (e.g., `v1.8.0` → `v1.8.1`) are auto-merged after CI passes. Minor and major updates require manual review. Configuration lives in `renovate.json`. +- **Go version pinning** — All CI workflows use `go-version-file: go.mod` so the Go compiler version is controlled by the `toolchain` directive in `go.mod`. To upgrade Go, update `go.mod` (Renovate opens PRs for this automatically). To downgrade after a bad release, revert the `toolchain` line in `go.mod` — all workflows pick up the change immediately. + +## Release Process In order to standardize the release process, [goreleaser](https://goreleaser.com) has been adopted. To build and release a new version: ``` git tag vX.X.X && git push --tags -gorelease --rm-dist +goreleaser release --clean ``` The binary version is set automatically to the git tag. Please tag new versions using [semantic versioning](https://semver.org/spec/v2.0.0.html). + +## Development Notes + +### PersistentPreRunE and subcommands + +The root command defines a `PersistentPreRunE` that loads configuration from flags, environment variables, and config files (see `cmd/root.go`). In cobra, if a subcommand defines its own `PersistentPreRunE`, it **overrides** the parent's — the root's `PersistentPreRunE` will not run for that subcommand or its children. If you need to add a `PersistentPreRunE` to a subcommand, you must explicitly call the parent's first. + +## Additional Reading + +The `docs/` directory contains write-ups on non-obvious problems encountered during development: + +- [OAuth Local Server: Unhandled Serve() Error](docs/oauth-serve-error.md) — Why the `Serve` goroutine error is captured via a buffered channel, and why the channel must be buffered. +- [OAuth Local Server Shutdown Deadlock](docs/oauth-shutdown-deadlock.md) — How an unbuffered channel creates a deadlock between the HTTP handler and `Shutdown()`, and the two-part fix. diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index a5d8b6d..58e7774 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -12,31 +12,31 @@ The command will return 0 in case of success or 1 in case of an error. ## Subcommands ### Authorize -imscli login will negotiate an ***access token*** with IMS following the specified ***flow***. +imscli authorize will negotiate an ***access token*** with IMS following the specified ***flow***. #### imscli authorize user (standard Authorization Code Grant Flow) -This command will launch a browser and execute the normal OAuth2 flow done by users when log into IMS to use a service. +This command will launch a browser and execute the normal OAuth2 flow done by users when logging into IMS to use a service. -#### imscli authz service (an IMS specific flow similar to the Client Credentials Grant Flow). +#### imscli authorize service (an IMS specific flow similar to the Client Credentials Grant Flow). The imscli client will exchange client credentials and an additional service token to obtain the access token. It is used to access an "Application", an Adobe API exposed through Adobe I/O Gateway. -#### imscli authz jwt (JWT Bearer Flow). +#### imscli authorize jwt (JWT Bearer Flow). -This command will build a JWT will all specified claims, sign it with a private key and exchange it for an access token. +This command will build a JWT with all specified claims, sign it with a private key and exchange it for an access token. It is used for "Adobe I/O" integrations. -### Completion +#### imscli authorize pkce (Authorization Code Grant Flow with PKCE) -Generate a script to enable autocompletion for imscli. +Like the user command, it uses the Authorization Code Grant Flow but with Proof Key for Code Exchange (PKCE). In IMS, PKCE is mandatory for public clients and recommended for private clients. -To configure bash, add the following line to your .bashrc (or alternative config file): +#### imscli authorize client (Client Credentials Grant Flow) - eval "$(imscli autocompletion bash)" +Exchanges client credentials (client ID + secret) and scopes directly for an access token, without user interaction. ### Profile @@ -59,6 +59,24 @@ Validates a token using the IMS API. Invalidates a token using the IMS API. +### Decode + +Decodes a JWT token locally, printing the header and payload without contacting IMS. + +### Refresh + +Refreshes an access token using a refresh token. + +### Admin + +Administrative operations using a service token: + +- **admin profile**: Retrieve a user profile using a service token, client ID, guid and auth source. +- **admin organizations**: Retrieve organizations for a user using a service token. + +### DCR (Dynamic Client Registration) + +Register a new OAuth client using Dynamic Client Registration. ## Configuration @@ -77,9 +95,9 @@ imscli authz user --scopes AdobeID,openid,session #### Environment variables -Each parameter can be provided using the flag name and the IMS_ suffix. +Each parameter can be provided using the flag name and the IMS_ prefix. ``` -IMS_SCOPES="AdobeID,openid,session" imscli login user +IMS_SCOPES="AdobeID,openid,session" imscli authorize user ``` #### Configuration files @@ -98,6 +116,6 @@ scopes: - openid - session -user@host$ imscli login user +user@host$ imscli authorize user ``` diff --git a/README.md b/README.md index 5652c8a..0693334 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,83 @@ # imscli ![CodeQL](https://github.com/adobe/imscli/workflows/CodeQL/badge.svg) +[![govulncheck](https://github.com/adobe/imscli/actions/workflows/govulncheck.yml/badge.svg)](https://github.com/adobe/imscli/actions/workflows/govulncheck.yml) +[![CI](https://github.com/adobe/imscli/actions/workflows/ci.yml/badge.svg)](https://github.com/adobe/imscli/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/adobe/imscli)](https://goreportcard.com/report/github.com/adobe/imscli) [![Release with GoReleaser](https://github.com/adobe/imscli/actions/workflows/main.yml/badge.svg)](https://github.com/adobe/imscli/actions/workflows/main.yml) +![Go Version](https://img.shields.io/github/go-mod/go-version/adobe/imscli) +[![Go Reference](https://pkg.go.dev/badge/github.com/adobe/imscli.svg)](https://pkg.go.dev/github.com/adobe/imscli) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) -This project is a small CLI tool to interact with the IMS API. The goal of this project -is to provide a small tool that can be used to troubleshoot integrations with IMS. - -This project is wrapping adobe/ims-go. +A CLI tool to troubleshoot and automate Adobe IMS integrations. ## Installation -Build the CLI or download a prebuilt [release](https://github.com/adobe/imscli/releases). +### Prebuilt binaries + +Download the latest release for your platform from the [releases page](https://github.com/adobe/imscli/releases). -Example: +### From source ```sh go install github.com/adobe/imscli@latest ``` -## Usage - -Once installed, you can start reading the integrated help with the help subcommand. +## Quick Start -Examples: - -``` -imscli help +```sh +# Authorize as a user (launches browser for OAuth2 flow) +imscli authorize user --clientID --clientSecret --organization --scopes openid -imscli authorize help +# Validate a token +imscli validate accessToken --clientID --accessToken -imscli authorize user help +# Decode a JWT locally (no API call) +imscli decode --token ``` -The complete documentation of the project is available in the [DOCUMENTATION.md](DOCUMENTATION.md) file. - -## Development Notes - -### PersistentPreRunE and subcommands - -The root command defines a `PersistentPreRunE` that loads configuration from flags, environment variables, and config files (see `cmd/root.go`). In cobra, if a subcommand defines its own `PersistentPreRunE`, it **overrides** the parent's — the root's `PersistentPreRunE` will not run for that subcommand or its children. If you need to add a `PersistentPreRunE` to a subcommand, you must explicitly call the parent's first. +## Commands + +| Command | Description | +|---------|-------------| +| `authorize user` | OAuth2 Authorization Code Grant Flow (launches browser) | +| `authorize pkce` | Authorization Code Grant Flow with PKCE (mandatory for public clients, optional for private) | +| `authorize service` | Service authorization (client credentials + service token) | +| `authorize jwt` | JWT Bearer Flow (signed JWT exchanged for access token) | +| `authorize client` | Client Credentials Grant Flow | +| `validate` | Validate a token using the IMS API | +| `invalidate` | Invalidate a token using the IMS API | +| `decode` | Decode a JWT token locally | +| `refresh` | Refresh an access token | +| `exchange` | Cluster access token exchange across IMS Orgs | +| `profile` | Retrieve user profile | +| `organizations` | List user organizations | +| `admin` | Admin operations (profile, organizations) via service token | +| `dcr` | Dynamic Client Registration | + +See [DOCUMENTATION.md](DOCUMENTATION.md) for full details on each command. + +## Global Flags + +These flags apply to all commands: + +| Flag | Short | Default | Description | +|------|-------|---------|-------------| +| `--url` | `-U` | `https://ims-na1.adobelogin.com` | IMS endpoint URL | +| `--proxyUrl` | `-P` | | HTTP(S) proxy (`http(s)://host:port`) | +| `--proxyIgnoreTLS` | `-T` | `false` | Skip TLS verification (proxy only) | +| `--configFile` | `-f` | | Configuration file path | +| `--timeout` | | `30` | HTTP client timeout in seconds | +| `--verbose` | `-v` | `false` | Verbose output | + +## Configuration + +Parameters can be provided from three sources (highest to lowest priority): + +1. **CLI flags** — `imscli authorize user --scopes openid` +2. **Environment variables** — `IMS_SCOPES=openid imscli authorize user` +3. **Configuration file** — `~/.config/imscli.yaml` or specified with `-f` + +See [DOCUMENTATION.md](DOCUMENTATION.md) for configuration file format and examples. ## Contributing diff --git a/cmd/pretty/testdata/already_indented.json b/cmd/pretty/testdata/already_indented.json new file mode 100644 index 0000000..7a9e864 --- /dev/null +++ b/cmd/pretty/testdata/already_indented.json @@ -0,0 +1,3 @@ +{ + "key": "value" +} diff --git a/cmd/pretty/testdata/compact_array.json b/cmd/pretty/testdata/compact_array.json new file mode 100644 index 0000000..3a11aa1 --- /dev/null +++ b/cmd/pretty/testdata/compact_array.json @@ -0,0 +1,8 @@ +[ + { + "id": 1 + }, + { + "id": 2 + } +] diff --git a/cmd/pretty/testdata/compact_object.json b/cmd/pretty/testdata/compact_object.json new file mode 100644 index 0000000..60dffeb --- /dev/null +++ b/cmd/pretty/testdata/compact_object.json @@ -0,0 +1,4 @@ +{ + "name": "John", + "age": 30 +} diff --git a/cmd/pretty/testdata/empty_array.json b/cmd/pretty/testdata/empty_array.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/pretty/testdata/empty_array.json @@ -0,0 +1 @@ +[] diff --git a/cmd/pretty/testdata/empty_object.json b/cmd/pretty/testdata/empty_object.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/cmd/pretty/testdata/empty_object.json @@ -0,0 +1 @@ +{} diff --git a/cmd/pretty/testdata/nested_objects.json b/cmd/pretty/testdata/nested_objects.json new file mode 100644 index 0000000..b2a72ab --- /dev/null +++ b/cmd/pretty/testdata/nested_objects.json @@ -0,0 +1,7 @@ +{ + "a": { + "b": { + "c": 1 + } + } +} diff --git a/cmd/pretty/testdata/null_and_bool.json b/cmd/pretty/testdata/null_and_bool.json new file mode 100644 index 0000000..a28ba15 --- /dev/null +++ b/cmd/pretty/testdata/null_and_bool.json @@ -0,0 +1,5 @@ +{ + "value": null, + "enabled": true, + "disabled": false +} diff --git a/cmd/pretty/testdata/special_chars.json b/cmd/pretty/testdata/special_chars.json new file mode 100644 index 0000000..754a0c9 --- /dev/null +++ b/cmd/pretty/testdata/special_chars.json @@ -0,0 +1,6 @@ +{ + "html": "\u003cscript\u003ealert('xss')\u003c/script\u003e", + "newlines": "line1\nline2", + "tabs": "col1\tcol2", + "quotes": "she said \"hello\"" +} diff --git a/cmd/pretty/testdata/unicode.json b/cmd/pretty/testdata/unicode.json new file mode 100644 index 0000000..49a6d23 --- /dev/null +++ b/cmd/pretty/testdata/unicode.json @@ -0,0 +1,5 @@ +{ + "greeting": "こんにちは", + "emoji": "🎉", + "accented": "café" +} diff --git a/ims/authz_user.go b/ims/authz_user.go index 07685e2..452e946 100644 --- a/ims/authz_user.go +++ b/ims/authz_user.go @@ -85,13 +85,13 @@ func (i Config) authorizeUser(pkce bool) (string, error) { UsePKCE: pkce, RedirectURI: fmt.Sprintf("http://localhost:%d", i.Port), OnError: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, ` + _, _ = fmt.Fprintln(w, `

An error occurred

Please look at the terminal output for further details.

`) }), OnSuccess: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, ` + _, _ = fmt.Fprintln(w, `

Login successful!

You can close this tab.

`) @@ -105,7 +105,7 @@ func (i Config) authorizeUser(pkce bool) (string, error) { if err != nil { return "", fmt.Errorf("unable to listen at port %d", i.Port) } - defer listener.Close() + defer func() { _ = listener.Close() }() log.Println("Local server successfully launched and contacted.") diff --git a/ims/register.go b/ims/register.go index 7f70e8c..1590231 100644 --- a/ims/register.go +++ b/ims/register.go @@ -59,7 +59,7 @@ func (i Config) Register() (RegisterResponse, error) { if err != nil { return RegisterResponse{}, fmt.Errorf("error making registration request: %v", err) } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() body, err := io.ReadAll(res.Body) if err != nil { diff --git a/renovate.json b/renovate.json index c3bdc63..35384c9 100644 --- a/renovate.json +++ b/renovate.json @@ -6,5 +6,12 @@ "postUpdateOptions": [ "gomodTidy", "gomodUpdateImportPaths" + ], + "packageRules": [ + { + "description": "Auto-merge patch updates after CI passes", + "matchUpdateTypes": ["patch"], + "automerge": true + } ] }