Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .golangci-kal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ linters:
- path: 'pkg/api/v1alpha1/sandboxconfig_types\.go'
text: '^(optionalfields|requiredfields): .*\bSandboxConfigSpec\.SandboxClass\b'
- path: 'pkg/api/v1alpha1/sandboxconfig_types\.go'
text: '^(nomaps|optionalfields): .*\bSandboxVersionConfig\.(Assets|PauseImage)\b'
text: '^(nomaps|optionalfields): .*\bSandboxVersionConfig\.Assets\b'
- path: 'pkg/api/v1alpha1/sandboxconfig_types\.go'
text: '^(nonpointerstructs|requiredfields): .*\bSandboxConfig\.Spec\b'
- path: 'pkg/api/v1alpha1/csidriverconfig_types\.go'
Expand Down
2 changes: 1 addition & 1 deletion benchmarking/analysis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ restore between `volume_mount`, `manifest_fetch`, `sandbox_assets`,
`prep` / `pause` / `snapshot` / `durable_dir` / `rootfs_upper` / `teardown`
on a checkpoint and `prep` / … / `vm_restore` / `wakeup_probe` on a restore;
for gVisor `pause` / `checkpoint` / `fs_checkpoint` / `resume` / `teardown` and
`prep` / `net_setup` / … / `pause_restore` / `app_restore` /
`prep` / `net_setup` / … / `sandbox_restore` / `app_restore` /
`wakeup_probe` / `activate` (the full lists are in
`cmd/ateworker-*/phaselog.go`). The ateworker
records carry no sandbox class or kind of their own; a paired one takes both
Expand Down
2 changes: 1 addition & 1 deletion benchmarking/analysis/phase_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
# ateworker restore: micro-VM (cmd/ateworker-microvm/phaselog.go) and gVisor
# (cmd/ateworker-gvisor/phaselog.go), each in its own sequential order
"prep", "egress_prepare", "net_setup", "bundles", "upper_join", "lowers",
"pause_rootfs", "pause_create", "pause_restore",
"sandbox_create", "sandbox_restore",
"app_rootfs", "app_create", "app_restore",
"tap", "vmm_launch", "vm_restore", "resume", "wakeup_probe", "activate",
# atelet + ateworker checkpoint
Expand Down
6 changes: 3 additions & 3 deletions cmd/ate-setup/differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ The rewritten reference then keeps that tag and adds the digest it names, as
tagged release. Resolving the digest is one HEAD request per image, cached for
the install, so `--image-repo` needs read access to the registry from wherever
`ate-setup` runs rather than only from the cluster. It is not optional: an
ActorTemplate's container image, an image volume's reference, and a
SandboxConfig's `pauseImage` each carry the CEL rule `self.contains('@')`, so an
unpinned reference is rejected by admission and every demo fails to deploy.
ActorTemplate's container image and an image volume's reference each carry the
CEL rule `self.contains('@')`, so an unpinned reference is rejected by admission
and every demo fails to deploy.
Pinning also gives a pre-built install the property a `ko` install had for free,
that a tag moving afterwards cannot change what is running.

Expand Down
14 changes: 7 additions & 7 deletions cmd/ate-setup/internal/images/prebuilt.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ func (p *Prebuilt) ResolveBytes(ctx context.Context, manifest []byte) ([]byte, e
// it, such as envoy-dataplane: a release publishes that under the same
// repository and tag as the rest, so it is found the same way.
//
// The digest is not decoration. An ActorTemplate's container image, an image
// volume's reference, and a SandboxConfig's pauseImage each carry the CEL rule
// self.contains('@'), so a tag on its own is rejected by admission and every
// demo would fail to deploy. Pinning also restores what ko gave the control
// plane deployments for free: an install that cannot shift under a tag someone
// moves later. Keeping the tag alongside the digest is ko's own output shape,
// and leaves the reference legible in `kubectl get`.
// The digest is not decoration. An ActorTemplate's container image and an image
// volume's reference each carry the CEL rule self.contains('@'), so a tag on
// its own is rejected by admission and every demo would fail to deploy. Pinning
// also restores what ko gave the control plane deployments for free: an install
// that cannot shift under a tag someone moves later. Keeping the tag alongside
// the digest is ko's own output shape, and leaves the reference legible in
// `kubectl get`.
func (p *Prebuilt) Pin(ctx context.Context, image string) (string, error) {
tagged := p.src.Repo + "/" + image + ":" + p.src.Tag
// A tag that already carries one is pinned as it stands. Looking it up
Expand Down
8 changes: 4 additions & 4 deletions cmd/ate-setup/internal/images/prebuilt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,10 @@ func TestResolveEveryInstallManifest(t *testing.T) {
if refs := koRefPattern.FindAllString(string(out), -1); len(refs) > 0 {
t.Errorf("unresolved references survived: %v", refs)
}
// Every reference has to name a digest. ActorTemplate images,
// image volume references, and a SandboxConfig's pauseImage all
// carry the CEL rule self.contains('@'), so an unpinned reference
// is not applied at all, it is rejected by admission.
// Every reference has to name a digest. ActorTemplate images and
// image volume references carry the CEL rule self.contains('@'), so
// an unpinned reference is not applied at all, it is rejected by
// admission.
for _, ref := range resolvedRef.FindAllString(string(out), -1) {
if !strings.Contains(ref, "@sha256:") {
t.Errorf("%s is not pinned to a digest", ref)
Expand Down
7 changes: 1 addition & 6 deletions cmd/ateapi/internal/controlapi/functionaltest/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,6 @@ func tagSnapshotURI(tag *ateapipb.Tag) string {
return tag.GetStatus().GetSnapshot().GetDurableSnapshot().GetObject().GetSnapshotUri()
}

// testPauseImage is the pause image the default test SandboxConfig carries;
// it is what a resolved WorkloadSpec's sandbox assets should name.
const testPauseImage = "pause@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

// ensureDefaultGvisorSandboxConfig creates the cluster-scoped "gvisor-default"
// SandboxConfig (idempotently) and waits for it to appear in the lister.
func ensureDefaultGvisorSandboxConfig(t *testing.T, tc *testContext) {
Expand All @@ -627,8 +623,7 @@ func ensureGvisorSandboxConfig(t *testing.T, tc *testContext, name string) {
SandboxClass: atev1alpha1.SandboxClassGvisor,
DefaultVersion: "v1",
Versions: []atev1alpha1.SandboxVersionConfig{{
Name: "v1",
PauseImage: testPauseImage,
Name: "v1",
Assets: map[string]map[string]atev1alpha1.AssetFile{
"amd64": {"gvisor": {
URL: "gs://gvisor/releases/nightly/2026-10-10/x86_64/gvisor.tar.zstd",
Expand Down
11 changes: 5 additions & 6 deletions cmd/ateapi/internal/controlapi/sandbox_assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ func resolvePoolSandboxConfig(
return sc, nil
}

// resolveSandboxAssets determines the sandbox binaries and pause image an actor
// boots with on its assigned worker and projects them onto the
// ateletpb.SandboxAssets atelet fetches: the default version of the
// SandboxConfig the worker's pool resolved for its sandbox class, which is the
// runtime the worker reports as its default.
// resolveSandboxAssets determines the sandbox binaries an actor boots with on
// its assigned worker and projects them onto the ateletpb.SandboxAssets atelet
// fetches: the default version of the SandboxConfig the worker's pool resolved
// for its sandbox class, which is the runtime the worker reports as its
// default.
//
// TODO: Remove with sandbox_assets. atelet already resolves the pool's
// SandboxConfig and its versions for every worker on the node (its sandbox
Expand Down Expand Up @@ -92,7 +92,6 @@ func resolveSandboxAssets(
func sandboxAssetsProto(sc *atev1alpha1.SandboxConfig, ver *atev1alpha1.SandboxVersionConfig) *ateletpb.SandboxAssets {
out := &ateletpb.SandboxAssets{
SandboxClass: string(sc.Spec.SandboxClass),
PauseImage: ver.PauseImage,
Assets: make(map[string]*ateletpb.ArchAssets, len(ver.Assets)),
}
for arch, files := range ver.Assets {
Expand Down
41 changes: 20 additions & 21 deletions cmd/ateapi/internal/controlapi/sandbox_assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,22 +78,22 @@ func testAssets() map[string]map[string]atev1alpha1.AssetFile {
// TestResolveSandboxAssets pins the pool-side resolution: the SandboxConfig
// the assigned worker's pool resolved is taken at its default version, a
// missing default version, an unresolved pool, a missing pool or config is an
// error, and the pause image travels with the sandbox binaries.
// error.
func TestResolveSandboxAssets(t *testing.T) {
const namedPause = "gcr.io/gke-release/pause@sha256:named"
const v2SHA = "v2-sha"
namedConfig := &atev1alpha1.SandboxConfig{
ObjectMeta: metav1.ObjectMeta{Name: "gvisor-custom"},
Spec: atev1alpha1.SandboxConfigSpec{
SandboxClass: atev1alpha1.SandboxClassGvisor,
DefaultVersion: "v2",
Versions: []atev1alpha1.SandboxVersionConfig{{
Name: "v1",
PauseImage: "gcr.io/gke-release/pause@sha256:old",
Assets: testAssets(),
Name: "v1",
Assets: testAssets(),
}, {
Name: "v2",
PauseImage: namedPause,
Assets: testAssets(),
Name: "v2",
Assets: map[string]map[string]atev1alpha1.AssetFile{
"amd64": {"gvisor": {URL: "gs://bucket/gvisor.tar.zstd", SHA256: v2SHA}},
},
}},
},
}
Expand All @@ -111,10 +111,9 @@ func TestResolveSandboxAssets(t *testing.T) {
SandboxClass: atev1alpha1.SandboxClassGvisor,
DefaultVersion: "v1",
Versions: []atev1alpha1.SandboxVersionConfig{{
Name: "v1",
State: ptr.To(atev1alpha1.SandboxVersionStateDisabled),
PauseImage: namedPause,
Assets: testAssets(),
Name: "v1",
State: ptr.To(atev1alpha1.SandboxVersionStateDisabled),
Assets: testAssets(),
}},
},
}
Expand All @@ -130,14 +129,14 @@ func TestResolveSandboxAssets(t *testing.T) {
}

tests := []struct {
name string
assignment *ateapipb.WorkerAssignment
wantPauseImage string
wantErr string
name string
assignment *ateapipb.WorkerAssignment
wantSHA string
wantErr string
}{{
name: "pool with a resolved config",
assignment: assignment("custom"),
wantPauseImage: namedPause,
name: "pool with a resolved config",
assignment: assignment("custom"),
wantSHA: v2SHA,
}, {
name: "config without its default version",
assignment: assignment("no-default"),
Expand Down Expand Up @@ -178,8 +177,8 @@ func TestResolveSandboxAssets(t *testing.T) {
if err != nil {
t.Fatalf("resolveSandboxAssets() error: %v", err)
}
if got.GetPauseImage() != tt.wantPauseImage {
t.Errorf("pause image = %q, want %q", got.GetPauseImage(), tt.wantPauseImage)
if gotSHA := got.GetAssets()["amd64"].GetFiles()["gvisor"].GetSha256(); gotSHA != tt.wantSHA {
t.Errorf("amd64 gvisor sha256 = %q, want %q", gotSHA, tt.wantSHA)
}
})
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/ateapi/internal/controlapi/workflow_resume.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,8 +823,8 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou
return tele, err
}

// The sandbox binaries and pause image come from the assigned worker's
// pool SandboxConfig on every path, restores included.
// The sandbox binaries come from the assigned worker's pool SandboxConfig
// on every path, restores included.
sandboxAssets, err := resolveSandboxAssets(w.workerPoolLister, w.sandboxConfigLister, assignment)
if err != nil {
return tele, fmt.Errorf("while resolving sandbox assets: %w", err)
Expand Down
7 changes: 3 additions & 4 deletions cmd/ateapi/internal/controlapi/workflow_resume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1207,9 +1207,8 @@ func newWireCaptureWorkflow(t *testing.T, persistence store.Interface) (*ActorWo
SandboxClass: atev1alpha1.SandboxClassGvisor,
DefaultVersion: "v1",
Versions: []atev1alpha1.SandboxVersionConfig{{
Name: "v1",
PauseImage: "pause@sha256:abc",
Assets: testAssets(),
Name: "v1",
Assets: testAssets(),
}},
},
}})
Expand Down Expand Up @@ -1622,7 +1621,7 @@ func TestResumeActor_AteletWireRequest(t *testing.T) {
// assigned worker's pool SandboxConfig resolves to.
wantSandboxAssets := sandboxAssetsProto(
&atev1alpha1.SandboxConfig{Spec: atev1alpha1.SandboxConfigSpec{SandboxClass: atev1alpha1.SandboxClassGvisor}},
&atev1alpha1.SandboxVersionConfig{Name: "v1", PauseImage: "pause@sha256:abc", Assets: testAssets()},
&atev1alpha1.SandboxVersionConfig{Name: "v1", Assets: testAssets()},
)

for _, tt := range tests {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ import (
// testGvisorDefault is the gvisor class default TestMain creates.
const testGvisorDefault = "gvisor-default-test"

const testPauseImage = "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4"

// testHoldFinalizer keeps a SandboxConfig on the API server after it is
// deleted, so a test can observe how a config that is being deleted is treated.
const testHoldFinalizer = "test.ate.dev/hold"
Expand Down Expand Up @@ -2397,9 +2395,6 @@ func TestAddProtectionFinalizer(t *testing.T) {

func makeSandboxConfig(name string, class atev1alpha1.SandboxClass, isDefault bool) *atev1alpha1.SandboxConfig {
v := atev1alpha1.SandboxVersionConfig{Name: "v1"}
if class == atev1alpha1.SandboxClassGvisor {
v.PauseImage = testPauseImage
}
sc := &atev1alpha1.SandboxConfig{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: atev1alpha1.SandboxConfigSpec{
Expand Down
4 changes: 0 additions & 4 deletions cmd/atelet/internal/apivalidation/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,6 @@ func ValidateCustom_SandboxRuntime_SandboxClass(_ context.Context, _ operation.O
return resources.ValidateSandboxClass(fldPath, *value)
}

func ValidateCustom_SandboxAssets_PauseImage(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList {
return resources.ValidatePinnedImage(fldPath, *value)
}

func ValidateCustom_AssetFile_Sha256(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList {
if err := resources.ValidateAssetHash(*value); err != nil {
return field.ErrorList{field.Invalid(fldPath, *value, err.Error())}
Expand Down
18 changes: 5 additions & 13 deletions cmd/atelet/internal/apivalidation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const testSHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852
func validSandboxAssets(mutate ...func(*ateletpb.SandboxAssets)) *ateletpb.SandboxAssets {
a := &ateletpb.SandboxAssets{
SandboxClass: "gvisor",
PauseImage: testDigestImage,
Assets: map[string]*ateletpb.ArchAssets{
"amd64": {Files: map[string]*ateletpb.AssetFile{"gvisor": {Url: "gs://bucket/gvisor.tar.zstd", Sha256: testSHA256}}},
"arm64": {Files: map[string]*ateletpb.AssetFile{"gvisor": {Url: "gs://bucket/gvisor.tar.zstd", Sha256: testSHA256}}},
Expand Down Expand Up @@ -326,8 +325,8 @@ func TestValidateRunRequest(t *testing.T) {
want: field.ErrorList{field.Required(field.NewPath("sandbox_assets"), "")},
}, {
name: "sandbox asset errors surface at the sandbox_assets path",
obj: valid(func(r *ateletpb.RunRequest) { r.SandboxAssets.PauseImage = "registry.k8s.io/pause:3.10.2" }),
want: field.ErrorList{field.Invalid(field.NewPath("sandbox_assets", "pause_image"), nil, "")},
obj: valid(func(r *ateletpb.RunRequest) { r.SandboxAssets.SandboxClass = "kvm" }),
want: field.ErrorList{field.NotSupported[string](field.NewPath("sandbox_assets", "sandbox_class"), nil, nil)},
}, {
name: "invalid container name: path escape",
obj: valid(func(r *ateletpb.RunRequest) {
Expand Down Expand Up @@ -449,13 +448,6 @@ func TestValidateSandboxAssets(t *testing.T) {
field.TooLong(field.NewPath("sandbox_class"), nil, 63).WithOrigin("maxLength"),
field.NotSupported[string](field.NewPath("sandbox_class"), nil, nil),
},
}, {
name: "valid: no pause_image (microvm has no pause container)",
obj: validSandboxAssets(func(a *ateletpb.SandboxAssets) { a.PauseImage = "" }),
}, {
name: "pause_image not pinned by digest",
obj: validSandboxAssets(func(a *ateletpb.SandboxAssets) { a.PauseImage = "registry.k8s.io/pause:3.10.2" }),
want: field.ErrorList{field.Invalid(field.NewPath("pause_image"), nil, "")},
}, {
name: "missing assets",
obj: validSandboxAssets(func(a *ateletpb.SandboxAssets) { a.Assets = nil }),
Expand Down Expand Up @@ -594,7 +586,7 @@ func TestValidateCheckpointRequest(t *testing.T) {
}, {
name: "spec errors surface at the spec's path",
obj: valid(func(r *ateletpb.CheckpointRequest) {
r.Spec = &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "_pause", Image: testDigestImage}}}
r.Spec = &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "_sandbox", Image: testDigestImage}}}
}),
want: field.ErrorList{field.Invalid(field.NewPath("spec", "containers").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name")},
}, {
Expand Down Expand Up @@ -775,7 +767,7 @@ func TestValidateRestoreRequest(t *testing.T) {
}, {
name: "spec errors surface at the spec's path",
obj: valid(func(r *ateletpb.RestoreRequest) {
r.Spec = &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "_pause", Image: testDigestImage}}}
r.Spec = &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "_sandbox", Image: testDigestImage}}}
}),
want: field.ErrorList{field.Invalid(field.NewPath("spec", "containers").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name")},
}, {
Expand Down Expand Up @@ -1072,7 +1064,7 @@ func TestValidateTerminateRequest(t *testing.T) {
}, {
name: "spec errors surface at the spec's path",
obj: valid(func(r *ateletpb.TerminateRequest) {
r.Spec = &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "_pause", Image: testDigestImage}}}
r.Spec = &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "_sandbox", Image: testDigestImage}}}
}),
want: field.ErrorList{field.Invalid(field.NewPath("spec", "containers").Index(0).Child("name"), nil, "").WithOrigin("format=k8s-short-name")},
}, {
Expand Down
35 changes: 0 additions & 35 deletions cmd/atelet/internal/apivalidation/zz_generated.validation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading