mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
c10b6732d5
* feat: publish AWS service endpoint names in DNS and match gateway cert SANs
ec2.<region>.spinifex.internal and its sibling AWS service names (sts,
elasticloadbalancing, ecs, eks, acm) NXDOMAINed because the internal
suffix was only ever a cert SAN and Host-header match, never a
northstar zone. BootstrapBaseZone now seeds it as a third zone, guarded
three-way so a suffix colliding with the default or internal domain
seeds no duplicate.
The reconciler publishes each name as a set-valued A record spanning
every cluster node's own gateway address, since a service endpoint's
natural target is every node rather than one resource address that
answers the same from anywhere. ConfigArr.SetRecordSet backs this in
northstar; UpsertRecord's single-record semantics are untouched for
EC2/ELB/EKS/RDS. A non-IP gateway host is skipped rather than written
as a malformed record, and prune authority for the new class is scoped
to its own zone so it can never act on the base or private zone.
admin.AWSGWServiceDNSNames and handlers/dns.ServiceEndpointNames both
consume config.AWSGWServiceNames, so the cert SANs and the DNS records
they must match can never drift apart.
Depends on github.com/mulgadc/northstar#31 (ConfigArr.SetRecordSet);
go.mod pins that branch's commit until it merges and tags.
* refactor(config): rename AWS.InternalSuffix to AWS.ServicesDomain
ServicesDomain names the value IMDS serves at
/latest/meta-data/services/domain, the slot an AWS SDK substitutes
into {service}.{region}.{domain} for default endpoint resolution.
The old name read as a sibling of Northstar's compute.internal while
meaning something entirely different, which invited confusion.
The default changes from spinifex.internal to services.internal, role-
named like compute.internal (per-instance hostnames) now that
services.internal holds service endpoints instead.
Deployed clusters have internal_suffix baked into spinifex.toml and
their gateway cert SANs built from it, so LoadConfig now reads
services_domain, falls back to the legacy internal_suffix key when
that is unset, and only then applies the new default. Every consumer
of the field and the renamed DefaultAWSServicesDomain constant is
updated accordingly, including the spx admin init template and the
e2e test harness's own config loader.
* chore: pin northstar to the merged set-valued write path
116 lines
3.9 KiB
Go
116 lines
3.9 KiB
Go
//go:build e2e
|
|
|
|
package harness
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/service/ecr"
|
|
"github.com/mulgadc/spinifex/spinifex/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// ECRRegistrySuffix is the internal DNS suffix the registry host is built from.
|
|
// Defaults to the gateway's DefaultAWSServicesDomain; override per cluster with
|
|
// SPINIFEX_ECR_SUFFIX.
|
|
func ECRRegistrySuffix() string {
|
|
return getenv("SPINIFEX_ECR_SUFFIX", config.DefaultAWSServicesDomain)
|
|
}
|
|
|
|
// ECRRegistryRegion is the region label in the registry host. Mirrors the AWS
|
|
// client region.
|
|
func ECRRegistryRegion() string { return getenv("SPINIFEX_AWS_REGION", "ap-southeast-2") }
|
|
|
|
// ECRRegistryPort is the awsgw port appended to the registry host for OCI
|
|
// clients. Empty by default (production parity, :443); set SPINIFEX_ECR_PORT on
|
|
// dev clusters where awsgw binds a non-standard port (e.g. 9999).
|
|
func ECRRegistryPort() string { return getenv("SPINIFEX_ECR_PORT", "") }
|
|
|
|
// ECRRegistryHost returns {account}.dkr.ecr.{region}.{suffix}[:port] — the OCI
|
|
// registry endpoint docker/crane/skopeo address. The bare host must resolve to
|
|
// the awsgw bind IP (DNS or /etc/hosts) for the data-plane subtests to run.
|
|
func ECRRegistryHost(account string) string {
|
|
host := account + ".dkr.ecr." + ECRRegistryRegion() + "." + ECRRegistrySuffix()
|
|
if p := ECRRegistryPort(); p != "" {
|
|
host += ":" + p
|
|
}
|
|
return host
|
|
}
|
|
|
|
// ECRRepositoryURI returns the full push/pull reference for repo under account.
|
|
func ECRRepositoryURI(account, repo string) string {
|
|
return ECRRegistryHost(account) + "/" + repo
|
|
}
|
|
|
|
// CreateECRRepository creates repo (idempotent: an existing repo is treated as
|
|
// success) and registers best-effort teardown.
|
|
func CreateECRRepository(t *testing.T, c *AWSClient, repo string) {
|
|
t.Helper()
|
|
_, err := c.ECR.CreateRepository(&ecr.CreateRepositoryInput{
|
|
RepositoryName: aws.String(repo),
|
|
})
|
|
if err != nil && !strings.Contains(err.Error(), "RepositoryAlreadyExistsException") {
|
|
require.NoError(t, err)
|
|
}
|
|
t.Cleanup(func() { DeleteECRRepositoryBestEffort(c, repo) })
|
|
}
|
|
|
|
// DeleteECRRepositoryBestEffort force-deletes repo and all its images, swallowing
|
|
// errors — used for cleanup.
|
|
func DeleteECRRepositoryBestEffort(c *AWSClient, repo string) {
|
|
_, _ = c.ECR.DeleteRepository(&ecr.DeleteRepositoryInput{
|
|
RepositoryName: aws.String(repo),
|
|
Force: aws.Bool(true),
|
|
})
|
|
}
|
|
|
|
// ECRGetLoginPassword returns the decoded registry password (the JWT half of the
|
|
// authorization token) for docker login -u AWS --password-stdin.
|
|
func ECRGetLoginPassword(t *testing.T, c *AWSClient) string {
|
|
t.Helper()
|
|
out, err := c.ECR.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, out.AuthorizationData, "no authorization data returned")
|
|
raw, err := base64.StdEncoding.DecodeString(aws.StringValue(out.AuthorizationData[0].AuthorizationToken))
|
|
require.NoError(t, err)
|
|
user, pass, found := strings.Cut(string(raw), ":")
|
|
require.True(t, found && user == "AWS", "unexpected authorization token shape")
|
|
return pass
|
|
}
|
|
|
|
// ECRDescribeImageTags returns the set of image tags currently in repo.
|
|
func ECRDescribeImageTags(t *testing.T, c *AWSClient, repo string) []string {
|
|
t.Helper()
|
|
out, err := c.ECR.DescribeImages(&ecr.DescribeImagesInput{
|
|
RepositoryName: aws.String(repo),
|
|
})
|
|
require.NoError(t, err)
|
|
var tags []string
|
|
for _, img := range out.ImageDetails {
|
|
for _, tag := range img.ImageTags {
|
|
tags = append(tags, aws.StringValue(tag))
|
|
}
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// ECRWaitImageTag polls DescribeImages until tag appears in repo or the deadline
|
|
// passes.
|
|
func ECRWaitImageTag(t *testing.T, c *AWSClient, repo, tag string, timeout time.Duration) bool {
|
|
t.Helper()
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
for _, got := range ECRDescribeImageTags(t, c, repo) {
|
|
if got == tag {
|
|
return true
|
|
}
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
return false
|
|
}
|