feat: add the meshStack CLI and the API client it shares with the Terraform provider - #2
Conversation
meshStack enforces the Accept header soon, so we have to make sure to always provide it
includes adaptations from PR remarks
|
Coverage of the acceptance run against the meshStack backend, on
Uncovered functions
|
The client arrives as a git subtree rather than a copy, so its history comes along and a change made here can travel back with `git subtree push`. The subtree's history carries the files at the repository root, which is why a later pull needs a `git subtree split` of the provider first. CU-86cb61rzz Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> git-subtree-dir: client git-subtree-mainline: 8f6cc2f git-subtree-split: cf78ea1
b329ad1 to
9aec93c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Credential leakage, workflow injection, setting-resolution defects, and panic paths remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Introduces the meshStack CLI and shared Go API client, including unified authentication, profile handling, release automation, and acceptance-test integration.
Changes:
- Adds the
meshstackcommand with login/logout and session resolution. - Imports and modernizes the shared meshStack API client.
- Adds build, packaging, container, CI, and repository infrastructure.
File summaries
| File | Description |
|---|---|
Taskfile.yml |
Defines development and release tasks. |
README.md |
Documents installation and development. |
pkg/setting/source.go |
Exposes setting-source helpers. |
pkg/setting/setting.go |
Exposes configurable settings. |
pkg/profile/profile.go |
Exposes profile resolution. |
pkg/io/stderr.go |
Exposes contextual stderr handling. |
pkg/auth/session.go |
Exposes sessions and clients. |
pkg/auth/method.go |
Exposes authentication methods. |
meshstack-satellite.gradle |
Configures acceptance-test execution. |
internal/version/version.go |
Implements version parsing and comparison. |
internal/testutil/jsontest/jsontest.go |
Adds JSON test helper. |
internal/setting/source.go |
Defines setting sources. |
internal/setting/setting.go |
Defines typed settings and parsers. |
internal/setting/setting_test/setting.go |
Adds setting test source. |
internal/setting/setting_test.go |
Tests boolean parsing. |
internal/setting/resolve.go |
Implements source precedence. |
internal/setting/env.go |
Implements environment lookup. |
internal/profile/testdata/jwt.json |
Adds profile JWT fixture. |
internal/profile/testdata/configdir/profiles.json |
Adds profile fixture. |
internal/profile/testdata/configdir/credentials/dev-local.json |
Adds credential fixture. |
internal/profile/testdata/configdir/credentials-cache/dev-local/apiKey.json |
Adds token-cache fixture. |
internal/profile/testdata/configdir/.gitignore |
Ignores fixture lock artifacts. |
internal/profile/resolve.go |
Resolves or creates profiles. |
internal/profile/profiles.go |
Loads and stores profiles. |
internal/profile/profile.go |
Defines profile behavior. |
internal/profile/name.go |
Defines profile names and help. |
internal/oidc/scope/scope.go |
Models OIDC scopes. |
internal/oidc/jwt/testdata/jwt_unscoped.json |
Adds unscoped JWT fixture. |
internal/oidc/jwt/testdata/jwt_unscoped_no_exp.json |
Adds no-expiry JWT fixture. |
internal/oidc/jwt/testdata/jwt_scoped.json |
Adds scoped JWT fixture. |
internal/oidc/jwt/testdata/jwt_opaque.json |
Adds opaque-token fixture. |
internal/oidc/jwt/testdata/jwt_not_json.json |
Adds malformed-payload fixture. |
internal/oidc/jwt/testdata/jwt_not_base64.json |
Adds invalid-base64 fixture. |
internal/oidc/jwt/jwt.go |
Parses JWT payloads. |
internal/oidc/jwt/jwt_test.go |
Tests JWT parsing and claims. |
internal/oidc/jwt/claim.go |
Defines typed JWT claims. |
internal/oidc/client.go |
Implements OIDC token operations. |
internal/oidc/browser/open_windows.go |
Opens browsers on Windows. |
internal/oidc/browser/open_linux.go |
Opens browsers on Linux. |
internal/oidc/browser/open_darwin.go |
Opens browsers on macOS. |
internal/oidc/browser/callback.html |
Defines login callback page. |
internal/oidc/browser/callback.go |
Renders callback responses. |
internal/oidc/authcode.go |
Implements PKCE authorization flow. |
internal/meshstack/workspaces.go |
Models selectable workspaces. |
internal/meshstack/workspace.go |
Defines workspace settings. |
internal/meshstack/settings.go |
Defines endpoint/version settings. |
internal/lock/readonly_windows.go |
Detects Windows read-only errors. |
internal/lock/readonly_other.go |
Detects Unix read-only errors. |
internal/lock/lock.go |
Implements process-safe locking. |
internal/json/unmarshal.go |
Adds JSON file decoding. |
internal/json/marshal.go |
Adds atomic JSON persistence. |
internal/json/marshal_test.go |
Tests persisted permissions. |
internal/io/stderr.go |
Stores stderr in context. |
internal/http/retry_test.go |
Tests retry backoff behavior. |
internal/http/method.go |
Re-exports HTTP methods. |
internal/http/logging.go |
Formats HTTP debug logs. |
internal/http/logging_test.go |
Tests authorization redaction. |
internal/http/logging_internal_test.go |
Tests deferred log rendering. |
internal/http/http_error.go |
Defines HTTP response errors. |
internal/http/auth.go |
Adds authorized request retries. |
internal/config/directory.go |
Resolves configuration paths. |
internal/auth/workspace_test.go |
Tests credential context propagation. |
internal/auth/version_check.go |
Checks for newer releases. |
internal/auth/credentials.go |
Resolves authentication credentials. |
internal/auth/credential/oidclogin_test.go |
Tests OIDC scope selection. |
internal/auth/credential/name.go |
Defines credential names. |
internal/auth/credential/manual.go |
Implements static-token credentials. |
internal/auth/credential/identity.go |
Hashes credential identities. |
internal/auth/credential/credential.go |
Defines credential storage types. |
internal/auth/credential/cache.go |
Manages in-memory credential caches. |
internal/auth/credential/apikey.go |
Implements API-key authentication. |
internal/auth/credential_oidclogin.go |
Resolves browser credentials. |
internal/auth/credential_manual.go |
Resolves manual tokens. |
internal/auth/credential_apikey.go |
Resolves API-key settings. |
internal/auth/auth.go |
Refreshes and caches bearer tokens. |
infra/github/terraform.tf |
Configures GitHub Terraform provider. |
infra/github/main.tf |
Defines repository protections. |
flake.nix |
Defines Nix builds and shell. |
flake.lock |
Pins Nix dependencies. |
Dockerfile |
Builds the runtime image. |
cmd/meshstack/meshstack.go |
Defines the CLI root command. |
cmd/internal/version.go |
Resolves CLI build version. |
cmd/internal/testacc/credentials_test.go |
Tests credential resolution acceptance paths. |
cmd/internal/session.go |
Connects CLI flags to sessions. |
cmd/internal/run.go |
Adds timeout and signal handling. |
cmd/internal/flags.go |
Defines shared CLI flags. |
cmd/internal/client.go |
Resolves command API clients. |
cmd/auth/logout.go |
Adds logout command. |
cmd/auth/auth.go |
Adds authentication command group. |
client/workspace.go |
Adds workspace API client. |
client/workspace_user_binding.go |
Adds workspace-user bindings. |
client/workspace_group_binding.go |
Adds workspace-group bindings. |
client/workspace_binding.go |
Defines workspace binding DTOs. |
client/types/xurl/url.go |
Adds validated URL type. |
client/types/variant/variant.go |
Adds polymorphic JSON values. |
client/types/clienttypes.go |
Defines shared client types. |
client/types/clienttypes_test.go |
Tests set-type detection. |
client/testdata/building_block_definition_version_input/sensitive.json |
Adds sensitive-input fixture. |
client/testdata/building_block_definition_version_input/sensitive_but_no_hash.json |
Adds invalid-sensitive fixture. |
client/testdata/building_block_definition_version_input/not_sensitive.json |
Adds ordinary-input fixture. |
client/testdata/building_block_definition_version_input/not_sensitive_but_hash.json |
Adds hash-shaped input fixture. |
client/testdata/building_block_definition_version_input/empty.json |
Adds empty-input fixture. |
client/tenant_v4_deletion_test.go |
Tests tenant deletion states. |
client/tag_definition.go |
Adds tag-definition API client. |
client/service_instance.go |
Adds service-instance API client. |
client/refs.go |
Defines meshObject references. |
client/project.go |
Adds project API client. |
client/project_user_binding.go |
Adds project-user bindings. |
client/project_group_binding.go |
Adds project-group bindings. |
client/project_binding.go |
Defines project binding DTOs. |
client/platform_type.go |
Adds platform-type API client. |
client/platform_properties_openshift.go |
Defines OpenShift properties. |
client/platform_properties_kubernetes.go |
Defines Kubernetes properties. |
client/platform_properties_gcp.go |
Defines GCP properties. |
client/platform_properties_custom.go |
Defines custom-platform properties. |
client/platform_properties_azurerg.go |
Defines Azure RG properties. |
client/platform_properties_azure.go |
Defines Azure properties. |
client/platform_properties_aws.go |
Defines AWS properties. |
client/platform_properties_aks.go |
Defines AKS properties. |
client/platform_config_openshift.go |
Defines OpenShift configuration. |
client/platform_config_kubernetes.go |
Defines Kubernetes configuration. |
client/platform_config_gcp.go |
Defines GCP configuration. |
client/platform_config_custom.go |
Defines custom-platform configuration. |
client/platform_config_azurerg.go |
Defines Azure RG configuration. |
client/platform_config_azure.go |
Defines Azure configuration. |
client/platform_config_aks.go |
Defines AKS configuration. |
client/payment_method.go |
Adds payment-method API client. |
client/mesh_info.go |
Adds backend information client. |
client/location.go |
Adds location API client. |
client/integration.go |
Adds integration API client. |
client/integration_config.go |
Defines integration configurations. |
client/client.go |
Composes the shared API client. |
client/client_kind.go |
Defines meshObject kind constants. |
client/client_kind_test.go |
Tests inferred object kinds. |
client/buildingblock.go |
Adds legacy building-block client. |
client/building_block_runner.go |
Adds runner API client. |
client/building_block_run.go |
Adds run-log API client. |
client/building_block_definition_version_test.go |
Tests definition input decoding. |
client/api_key.go |
Adds API-key resource client. |
.goreleaser.yml |
Configures release artifacts. |
.gitignore |
Ignores generated and local files. |
.github/workflows/test-acceptance.yml |
Dispatches backend acceptance tests. |
.github/workflows/release.yml |
Publishes tagged releases. |
.github/workflows/build-image.yml |
Builds and publishes container images. |
.dockerignore |
Minimizes Docker build context. |
.claude/settings.json |
Formats edited Go files. |
Review details
Suppressed comments (1)
client/types/clienttypes_test.go:28
- This case is labeled
Set[int]but passesSet[string]again, leaving the integer instantiation untested.
- Files reviewed: 180/183 changed files
- Comments generated: 10
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The client keeps its public shape, and a caller that held a `client.Client` still does. What changes is where it gets its parts from: the HTTP client, the request options and the retry policy move one directory up into `internal/http`, so that `internal/oidc` and `pkg/auth` can use them too — Go's internal rule closes `client/internal` to both. Authorization becomes an interface the client is handed rather than a login it performs itself, which is what lets one resolved session decide the endpoint and the token together. Bodies marshal with `encoding/json/v2`, and the `apiVersion` and `kind` members now come from an `,embed` tag rather than from a hand-written wrapper struct per request. This commit does not build on its own: `go.mod` arrives in the next one. CU-86cb61rzz Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2ac7bf6 to
3c68e98
Compare
3c68e98 to
9f2ee06
Compare
7750330 to
4b9a456
Compare
The repository becomes a Go module, with the toolchain pinned in go.mod and in the nix flake, a lint gate that also formats, a release pipeline for the archives and the container image, and the GitHub ruleset under terraform. The HTTP, JSON and version packages the client compiles against come along in this commit rather than a later one, so that the tree builds from here on. They sit outside `client/` because the login flow and the settings layer need them as well, and Go's internal rule would close `client/internal` to both. CU-86cb61rzz Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ials Both front ends need the same answer to one question: which endpoint, which workspace and which token does this invocation use. A session resolves that once, from the environment, a stored profile and whatever the front end itself offers, and hands out a client that already carries the result. Each setting is declared once, in the package that owns it, so a message naming an environment variable is written where the variable is defined. A credential is resolved by name. Two sources that carry different values for the same setting raise an error that names both, rather than one quietly winning. A minted token is cached in the config directory under a file lock, so concurrent runs share one token instead of each asking for its own, and the browser login refreshes before expiry rather than sending the person back to keycloak. CU-86cb61rzz Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`meshstack auth login` opens a browser, lets the person pick a workspace and writes the profile and its cached token to the config directory; `meshstack auth logout` ends that session and drops the token. `meshstack login` and `meshstack logout` are the same commands registered a second time at the top level, because cobra's aliases only rename a command within its parent. The command tree is the CLI's one settings source: every flag it defines resolves through the same mechanism the environment and the stored profile go through, so a flag and an environment variable that disagree produce the same error here as anywhere else. CU-86cb61rzz Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A person who installed the binary once has no other way to learn that a release fixed the thing they are hitting. The check runs at most once a day, remembers the answer in the config directory, and never fails the command it rides on: a GitHub outage or an offline machine leaves the invocation alone. It sits beside the session rather than in its own package because the release lookup needs the shared HTTP client, and the client's dependency rules keep that reachable only from here. CU-86cb61rzz Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The browser login is the one flow no unit test reaches: it needs a real keycloak to answer the authorization request and a real meshStack to accept the token that comes back. This suite builds the binary, plays the browser against keycloak's HTML forms with its own cookie jar, and then checks that the profile and the cached token it wrote let a second invocation run without logging in again. meshfed-release runs it as a satellite suite, because a whole meshStack only exists there. It skips without one, so `go test ./...` stays green here. CU-86cb61rzz Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The profile setting becomes part of the surface the Terraform provider imports, which declares a profile block attribute from it, so the declaration stays in the package that owns profiles and each front end only contributes a source. As a front end source the flag outranks MESHSTACK_PROFILE, the profile matching the endpoint and the last selected profile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Profiles were only ever created on first-time use, when there is no configuration file at all, so a name other than the default named a profile that could never come to exist and --profile had almost nothing to select. A login is the command that writes a profile, so it is the one that may create one; for every other command an unknown name is a typo, and an empty profile written from it would hide the mistake instead of reporting it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Terraform provider contributes setting sources but holds no session, so it cannot ask which credential a run authenticates with. It needs that to look a workspace up only for a browser login, where a workspace is what scopes the token, and the context carrying the workspaces to pick from is where the answer already belongs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4b9a456 to
b8e05c3
Compare
This repository becomes the home of the meshStack API client and of the
meshstackbinary. Theclient moves out of the Terraform provider as a git subtree, so both front ends build against one
client instead of each carrying its own, and the authentication that used to sit inside it becomes
a session both of them resolve the same way. The CLI itself ships with
auth loginandauth logout.flowchart LR subgraph cli["meshstack-cli"] session["session: settings, profile, credential"] client["client/ (git subtree)"] session --> client end binary["meshstack binary"] --> session provider["terraform-provider-meshstack"] --> session release["meshfed-release"] -->|runs the acceptance suite| binary provider -.->|git subtree push / pull| clientA session answers one question for both front ends: which endpoint, which workspace and which
token this invocation uses. Each setting is declared once, in the package that owns it, and each
front end contributes one source over its own flags or block attributes. A setting takes its value
from the first source that carries one, in a fixed order, while two credentials that resolve at the
same time are an error naming both rather than one quietly winning. A minted token is cached under
a file lock, so concurrent runs share it.
Reading order
The history was rebuilt for commit-by-commit review, so read it in order:
encoding/json/v2, HTTP and authorization moved outauthcommandsCommits 1 and 2 do not build on their own, because
go.modonly arrives in commit 3. Arepository bootstrapped in this order cannot avoid that: the subtree add has to land on the empty
root commit for the client's history to come along, and the client's own edits belong next to it
rather than mixed into the module setup. Commit 3 onwards builds, and
go build ./...was run oneach.
What a reviewer cannot see
task lintreports no issue andtask testpasses at the tip. The acceptance suite was not runhere — it needs a live meshStack, which only meshfed-release has.
A few doc comments in
client/are reworded rather than moved unchanged.godoclintandstaticcheckST1021 reject the provider's original wording, so the change is forced by thisrepository's lint configuration.
.github/workflows/test-acceptance.ymlstill listsfeature/scaffold-clias a push trigger. Apull_request_targetrun reads the workflow from the base branch, where the file does not existyet, so a push is the only trigger that can try the dispatcher out. Delete that line before merge.
Paired by branch name with meshcloud/meshfed-release#10866, whose credentials the acceptance suite
reads, and with meshcloud/terraform-provider-meshstack#299, which consumes this.
ClickUp: 86cb61rzz, milestone
86cb61we4.
🤖 Generated with Claude Code