3 Commits

Author SHA1 Message Date
99817dc25d Merge pull request 'Fix CLAUDE.md: correct binary names and build targets' (#2) from fix/claude-md-factual-errors into master 2026-04-02 22:20:23 +00:00
cf8011196f Fix CLAUDE.md: correct binary names and build targets
- Fix cmd/mcr/ to cmd/mcrsrv/ (actual directory name)
- Fix make mcr to make mcrsrv, add mcr-web and mcrctl targets
- Add cmd/mcrctl/ to package structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:14:43 -07:00
1f3bcd6b69 Fix gRPC auth: inject bearer token via PerRPCCredentials
The gRPC client was not sending the authorization header, causing
"missing authorization header" errors even when a token was configured.
Also fix config test to isolate from real ~/.config/mcrctl.toml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 19:39:00 -07:00
3 changed files with 24 additions and 5 deletions

View File

@@ -12,7 +12,9 @@ MCR (Metacircular Container Registry) is a container registry service integrated
```bash
make all # vet → lint → test → build
make mcr # build binary with version injection
make mcrsrv # build API server binary with version injection
make mcr-web # build web UI binary with version injection
make mcrctl # build admin CLI binary
make build # compile all packages
make test # run all tests
make vet # go vet
@@ -53,8 +55,9 @@ go test ./internal/server -run TestPushManifest
## Package Structure
- `cmd/mcr/`CLI entry point (cobra subcommands: server, init, status, snapshot)
- `cmd/mcrsrv/`API server entry point (cobra subcommands: server, init, status, snapshot)
- `cmd/mcr-web/` — Web UI entry point
- `cmd/mcrctl/` — Admin CLI entry point
- `internal/auth/` — MCIAS integration (token validation, 30s cache by SHA-256)
- `internal/config/` — TOML config loading and validation
- `internal/db/` — SQLite setup, migrations (idempotent, tracked in `schema_migrations`)

View File

@@ -62,10 +62,14 @@ func newClient(serverURL, grpcAddr, token, caCertFile string) (*apiClient, error
if grpcAddr != "" {
creds := credentials.NewTLS(tlsCfg)
cc, err := grpc.NewClient(grpcAddr,
dialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
grpc.WithDefaultCallOptions(grpc.ForceCodecV2(mcrv1.JSONCodec{})),
)
}
if token != "" {
dialOpts = append(dialOpts, grpc.WithPerRPCCredentials(bearerToken(token)))
}
cc, err := grpc.NewClient(grpcAddr, dialOpts...)
if err != nil {
return nil, fmt.Errorf("grpc dial: %w", err)
}
@@ -91,6 +95,16 @@ func (c *apiClient) useGRPC() bool {
return c.grpcConn != nil
}
// bearerToken implements grpc.PerRPCCredentials, injecting the
// Authorization header into every gRPC call.
type bearerToken string
func (t bearerToken) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
return map[string]string{"authorization": "Bearer " + string(t)}, nil
}
func (t bearerToken) RequireTransportSecurity() bool { return true }
// apiError is the JSON error envelope returned by the REST API.
type apiError struct {
Error string `json:"error"`

View File

@@ -38,7 +38,9 @@ ca_cert = "/path/to/ca.pem"
}
func TestLoadConfigMissingDefaultIsOK(t *testing.T) {
// Empty path triggers default search; missing file is not an error.
// Point XDG_CONFIG_HOME at an empty dir so we don't find the real config.
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
cfg, err := loadConfig("")
if err != nil {
t.Fatal(err)