mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-21 03:36:35 +00:00
93db7e4d1b
* feat(ecs): ecs-agent skeleton + containerd image pull (Sprint 4c) Add the ecs-agent binary that runs inside an ECS container instance: it resolves host identity from IMDS, registers on the Layer-2 NATS bus, and heartbeats while alive. Image pulls go through a containerd-backed ImagePuller behind an interface, with an ECR resolver that mints GetAuthorizationToken creds from the instance role. Extract the IMDS-credential and ECR-auth flow out of ecr-credential-provider into internal/imdscreds and internal/ecrauth so the agent and the kubelet provider share one implementation. Add spinifex/handlers/ecs/bus as the single source of truth for Layer-2 subject builders and wire payloads, shared by the agent now and the scheduler in Sprint 4e. * build: pin genproto via replace to fix containerd/v2 ambiguous import go.opencensus.io -> grpc@v1.33.2 drags the pre-split monolithic genproto into the module graph; it still ships googleapis/rpc/status, which collides with the split genproto/googleapis/rpc module that containerd/ttrpc needs. The main module never imports the monolith, so a plain require bump is dropped by 'go mod tidy'. Pin it with a replace to a post-split version so the fix survives tidy and contributors can run 'go mod tidy' freely. * fix(ecs): agent boot failsafes from runtime verify - Build the ECR gateway HTTP client lazily on first image pull instead of at boot. A missing/malformed gateway CA no longer kills the agent before it can register + heartbeat, matching the best-effort containerd path. - Actively probe containerd with IsServing in runtime.New. The client dials lazily, so a dead socket previously slipped past the boot-time 'containerd unavailable' warn and only surfaced on the first pull. - Make the heartbeat interval configurable via ECS_HEARTBEAT_INTERVAL (default 30s) for test/verify and slow-link tuning.
60 lines
1.4 KiB
Go
60 lines
1.4 KiB
Go
package credentials
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/mulgadc/spinifex/internal/imdscreds"
|
|
)
|
|
|
|
// refreshMargin is how long before expiry the cache is considered stale.
|
|
const refreshMargin = 5 * time.Minute
|
|
|
|
// IMDSProvider fetches instance-role credentials from IMDS and caches them until
|
|
// they near expiry. Safe for concurrent use.
|
|
type IMDSProvider struct {
|
|
client *http.Client
|
|
base string
|
|
|
|
mu sync.Mutex
|
|
cached Credentials
|
|
}
|
|
|
|
var _ CredentialsProvider = (*IMDSProvider)(nil)
|
|
|
|
// NewIMDSProvider builds a provider against the given IMDS base URL (e.g.
|
|
// "http://169.254.169.254/latest"). A nil client uses http.DefaultClient.
|
|
func NewIMDSProvider(client *http.Client, base string) *IMDSProvider {
|
|
if client == nil {
|
|
client = http.DefaultClient
|
|
}
|
|
return &IMDSProvider{client: client, base: base}
|
|
}
|
|
|
|
// Retrieve returns cached credentials when still valid, else fetches fresh ones.
|
|
func (p *IMDSProvider) Retrieve(ctx context.Context) (Credentials, error) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.cached.Valid(refreshMargin) {
|
|
return p.cached, nil
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return Credentials{}, err
|
|
}
|
|
|
|
raw, err := imdscreds.Fetch(p.client, p.base)
|
|
if err != nil {
|
|
return Credentials{}, err
|
|
}
|
|
p.cached = Credentials{
|
|
AccessKeyID: raw.AccessKeyID,
|
|
SecretAccessKey: raw.SecretAccessKey,
|
|
SessionToken: raw.SessionToken,
|
|
Expiration: raw.Expiration,
|
|
}
|
|
return p.cached, nil
|
|
}
|