fix(auth): complete Task 13 deployment review
This commit is contained in:
@@ -394,7 +394,8 @@ type renderedCompose struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"volumes"`
|
||||
Services map[string]struct {
|
||||
Image string `json:"image"`
|
||||
Image string `json:"image"`
|
||||
ContainerName string `json:"container_name"`
|
||||
} `json:"services"`
|
||||
}
|
||||
|
||||
@@ -474,18 +475,13 @@ func imageIdentities(ctx context.Context, installation config.Installation, runn
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matching := make([]composeImageIdentity, 0, len(inspected))
|
||||
for _, item := range inspected {
|
||||
if item.Service == "" || item.Service == name {
|
||||
matching = append(matching, item)
|
||||
}
|
||||
expectedContainer := rendered.Services[name].ContainerName
|
||||
if expectedContainer == "" {
|
||||
expectedContainer = installation.ProjectName() + "-" + name + "-1"
|
||||
}
|
||||
if len(matching) > 1 {
|
||||
return nil, errors.New("Docker Compose returned invalid image identities")
|
||||
}
|
||||
id := ""
|
||||
if len(matching) == 1 {
|
||||
id = matching[0].ID
|
||||
id, err := selectComposeImageIdentity(name, expectedContainer, inspected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
images = append(images, ImageIdentity{Service: name, Reference: rendered.Services[name].Image, ID: id})
|
||||
}
|
||||
@@ -500,7 +496,7 @@ type composeImageIdentity struct {
|
||||
|
||||
func decodeComposeImageIdentities(value string) ([]composeImageIdentity, error) {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
if trimmed == "" || trimmed == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
var identities []composeImageIdentity
|
||||
@@ -530,6 +526,22 @@ func decodeComposeImageIdentities(value string) ([]composeImageIdentity, error)
|
||||
return identities, nil
|
||||
}
|
||||
|
||||
func selectComposeImageIdentity(service string, expectedContainer string, identities []composeImageIdentity) (string, error) {
|
||||
if len(identities) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
matching := make([]composeImageIdentity, 0, len(identities))
|
||||
for _, item := range identities {
|
||||
if item.Service == service || item.Service == "" && item.ContainerName == expectedContainer {
|
||||
matching = append(matching, item)
|
||||
}
|
||||
}
|
||||
if len(matching) != 1 {
|
||||
return "", errors.New("Docker Compose returned invalid image identities")
|
||||
}
|
||||
return matching[0].ID, nil
|
||||
}
|
||||
|
||||
func installationRunning(ctx context.Context, installation config.Installation, runner archiveRunner) (bool, error) {
|
||||
result, err := runner.Run(ctx, installation.ComposeArgs("ps", "--all", "--format", "json"), nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
var requiredTestVolumes = []string{"settings", "pi-state", "workspace-registry", "workspace-secrets", "sessions", "qdrant-data", "embedding-models"}
|
||||
|
||||
func TestDecodeComposeImageIdentitiesAcceptsArrayAndStreamingJSON(t *testing.T) {
|
||||
func TestDecodeComposeImageIdentitiesAcceptsNonEmptyArrayAndStreamingJSON(t *testing.T) {
|
||||
for name, input := range map[string]string{
|
||||
"array": `[{"ContainerName":"project-core-1","ID":"sha256:core"},{"ContainerName":"project-frontend-1","ID":"sha256:frontend"}]`,
|
||||
"streaming": "{\"Service\":\"core\",\"ID\":\"sha256:core\"}\n{\"Service\":\"frontend\",\"ID\":\"sha256:frontend\"}\n",
|
||||
@@ -41,11 +41,72 @@ func TestDecodeComposeImageIdentitiesAcceptsArrayAndStreamingJSON(t *testing.T)
|
||||
}
|
||||
|
||||
func TestDecodeComposeImageIdentitiesAcceptsNoContainerForProfiledService(t *testing.T) {
|
||||
for _, input := range []string{"", "[]"} {
|
||||
identities, err := decodeComposeImageIdentities(input)
|
||||
if err != nil || len(identities) != 0 {
|
||||
t.Fatalf("decodeComposeImageIdentities(%q) = %#v, %v", input, identities, err)
|
||||
}
|
||||
for name, input := range map[string]string{
|
||||
"empty output": "",
|
||||
"empty array": "[]",
|
||||
"null": "null",
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
identities, err := decodeComposeImageIdentities(input)
|
||||
if err != nil || len(identities) != 0 {
|
||||
t.Fatalf("decodeComposeImageIdentities(%q) = %#v, %v", input, identities, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeComposeImageIdentitiesRejectsMalformedOrIncompleteOutput(t *testing.T) {
|
||||
for name, input := range map[string]string{
|
||||
"malformed JSON": "[",
|
||||
"scalar JSON": "true",
|
||||
"empty object": "{}",
|
||||
"null array element": "[null]",
|
||||
"missing image ID": `[{"Service":"core"}]`,
|
||||
"trailing document": "null\n{}",
|
||||
"trailing malformed bytes": `null garbage`,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if identities, err := decodeComposeImageIdentities(input); err == nil {
|
||||
t.Fatalf("decodeComposeImageIdentities(%q) = %#v, nil; want error", input, identities)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectComposeImageIdentityUsesExactComposeContainerWhenServiceIsAbsent(t *testing.T) {
|
||||
identities := []composeImageIdentity{
|
||||
{ContainerName: "project-core-1", ID: "sha256:core"},
|
||||
{ContainerName: "project-llm", ID: "sha256:shared-image"},
|
||||
}
|
||||
id, err := selectComposeImageIdentity("core", "project-core-1", identities)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id != "sha256:core" {
|
||||
t.Fatalf("selected image ID = %q, want sha256:core", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectComposeImageIdentityDoesNotFailOpen(t *testing.T) {
|
||||
for name, identities := range map[string][]composeImageIdentity{
|
||||
"unrelated container only": {{ContainerName: "project-llm", ID: "sha256:shared-image"}},
|
||||
"duplicate service": {
|
||||
{Service: "core", ID: "sha256:first"},
|
||||
{Service: "core", ID: "sha256:second"},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if id, err := selectComposeImageIdentity("core", "project-core-1", identities); err == nil {
|
||||
t.Fatalf("selectComposeImageIdentity() = %q, nil; want error", id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectComposeImageIdentityAcceptsSemanticallyEmptyOutput(t *testing.T) {
|
||||
id, err := selectComposeImageIdentity("workspace-maintenance", "project-workspace-maintenance-1", nil)
|
||||
if err != nil || id != "" {
|
||||
t.Fatalf("selectComposeImageIdentity() = %q, %v; want empty identity", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -572,6 +572,13 @@ func validateVolumeTar(ctx context.Context, member *zip.File) error {
|
||||
return fmt.Errorf("read volume archive: %w", err)
|
||||
}
|
||||
name := strings.TrimSuffix(header.Name, "/")
|
||||
if name == "." {
|
||||
if header.Typeflag != tar.TypeDir {
|
||||
return errors.New("volume archive root marker is not a directory")
|
||||
}
|
||||
continue
|
||||
}
|
||||
name = strings.TrimPrefix(name, "./")
|
||||
if _, err := validateArchiveMemberPath(name); err != nil {
|
||||
return fmt.Errorf("volume archive path is unsafe: %w", err)
|
||||
}
|
||||
|
||||
@@ -233,6 +233,60 @@ func TestPreflightRejectsTraversalAndSymlinkInsideVolumeTar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreflightAcceptsCanonicalTarRootDirectoryAndRelativeMembers(t *testing.T) {
|
||||
installation := preflightTestInstallation(t)
|
||||
var payload strings.Builder
|
||||
writer := tar.NewWriter(&stringWriter{value: &payload})
|
||||
for _, header := range []tar.Header{
|
||||
{Name: "./", Mode: 0o755, Typeflag: tar.TypeDir},
|
||||
{Name: "./payload", Mode: 0o600, Size: 1, Typeflag: tar.TypeReg},
|
||||
} {
|
||||
if err := writer.WriteHeader(&header); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if header.Size > 0 {
|
||||
if _, err := writer.Write([]byte("x")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
archive := filepath.Join(t.TempDir(), "canonical-volume.zip")
|
||||
writePreflightArchive(t, archive, preflightArchiveSpec{entries: []preflightArchiveEntry{{
|
||||
path: "volumes/sessions.tar", body: []byte(payload.String()), kind: EntryVolume,
|
||||
}}})
|
||||
result, err := Preflight(context.Background(), installation, PreflightRequest{Archive: archive, Confirm: true}, permissivePreflightDependencies())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result.CloseArchive()
|
||||
}
|
||||
|
||||
func TestPreflightRejectsNonDirectoryTarRootMarker(t *testing.T) {
|
||||
installation := preflightTestInstallation(t)
|
||||
var payload strings.Builder
|
||||
writer := tar.NewWriter(&stringWriter{value: &payload})
|
||||
header := tar.Header{Name: ".", Mode: 0o600, Size: 1, Typeflag: tar.TypeReg}
|
||||
if err := writer.WriteHeader(&header); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := writer.Write([]byte("x")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
archive := filepath.Join(t.TempDir(), "unsafe-root-volume.zip")
|
||||
writePreflightArchive(t, archive, preflightArchiveSpec{entries: []preflightArchiveEntry{{
|
||||
path: "volumes/sessions.tar", body: []byte(payload.String()), kind: EntryVolume,
|
||||
}}})
|
||||
if _, err := Preflight(context.Background(), installation, PreflightRequest{Archive: archive, Confirm: true}, permissivePreflightDependencies()); err == nil {
|
||||
t.Fatal("Preflight accepted a non-directory TAR root marker")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreflightRejectsArchivesThatExceedConfiguredProcessingLimits(t *testing.T) {
|
||||
installation := preflightTestInstallation(t)
|
||||
tests := []struct {
|
||||
|
||||
@@ -285,7 +285,7 @@ func safeRestoreParent(target string) bool {
|
||||
|
||||
func volumeRestoreCommand(volume string) []string {
|
||||
return []string{
|
||||
"run", "--rm", "--network", "none", "--mount", "type=volume,src=" + volume + ",dst=/target",
|
||||
"run", "--rm", "--interactive", "--network", "none", "--mount", "type=volume,src=" + volume + ",dst=/target",
|
||||
helperImage, "sh", "-ceu",
|
||||
"rm -rf -- /target/* /target/.[!.]* /target/..?*; tar --numeric-owner -C /target -xf -",
|
||||
}
|
||||
|
||||
@@ -30,6 +30,19 @@ func TestRestorePublicPathUsesConcreteProductionPreflight(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeRestoreCommandKeepsTarInputOpenWithoutTTY(t *testing.T) {
|
||||
args := volumeRestoreCommand("project_sessions")
|
||||
wantPrefix := []string{"run", "--rm", "--interactive", "--network", "none"}
|
||||
if len(args) < len(wantPrefix) || !equalStrings(args[:len(wantPrefix)], wantPrefix) {
|
||||
t.Fatalf("volumeRestoreCommand() prefix = %q, want %q", args, wantPrefix)
|
||||
}
|
||||
for _, arg := range args {
|
||||
if arg == "--tty" || arg == "-t" {
|
||||
t.Fatalf("volumeRestoreCommand() requests a TTY: %q", args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreRestoresVerifiedVolumesInManifestOrderBeforeAuthenticationReset(t *testing.T) {
|
||||
installation := preflightTestInstallation(t)
|
||||
archive := filepath.Join(t.TempDir(), "restore-volumes.zip")
|
||||
|
||||
Reference in New Issue
Block a user