From 72aca6e57a696fae56d1b2036b183854809a7941 Mon Sep 17 00:00:00 2001 From: chen21019 Date: Tue, 8 Sep 2026 20:10:28 +0800 Subject: [PATCH 1/2] Preserve YAML v3 service mappings --- .github/workflows/security-release-gate.yml | 6 +-- README.md | 2 +- config/interpolation.go | 20 ++++++++++ config/preprocess.go | 30 ++++++++++++++ config/yaml_v3_mapping_test.go | 44 +++++++++++++++++++++ docs/releases/compose-executor-0.14.36.md | 10 +++++ 6 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 config/yaml_v3_mapping_test.go create mode 100644 docs/releases/compose-executor-0.14.36.md diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index e464c269..9c2bea43 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -21,7 +21,7 @@ jobs: env: DAPPER_IMAGE: pasturestack/compose-cli-dapper:${{ github.sha }} TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 - VERSION_OVERRIDE: 0.14.35 + VERSION_OVERRIDE: 0.14.36 PLATFORM_COMPAT_JAR_URL: https://github.com/PastureStack/orchestration-engine/releases/download/v0.183.281/orchestration-engine-0.183.281.jar PLATFORM_COMPAT_JAR_SHA256: da2a8a51562ed16e296f7e29e99482bb44042ff0834cca679bbe01d951ba1682 @@ -69,7 +69,7 @@ jobs: } run_ci - artifact="dist/artifacts/compose-executor-0.14.35-linux-amd64.gz" + artifact="dist/artifacts/compose-executor-0.14.36-linux-amd64.gz" test -s "$artifact" cp "$artifact" /tmp/compose-executor-first.gz @@ -81,7 +81,7 @@ jobs: mkdir -p evidence/product gzip -cd "$artifact" > evidence/product/compose-executor chmod +x evidence/product/compose-executor - evidence/product/compose-executor --version | grep -F '0.14.35' >/dev/null + evidence/product/compose-executor --version | grep -F '0.14.36' >/dev/null sha256sum "$artifact" > evidence/compose-executor.gz.sha256 docker run --rm --entrypoint go \ --volume "$PWD:/work:ro" \ diff --git a/README.md b/README.md index 6e73107f..c9e300bc 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ make test make package ``` -Set `VERSION_OVERRIDE=0.14.33` for the current maintenance candidate. Packaging produces the deterministic, versioned `compose-executor-0.14.33-linux-amd64.gz` asset for a matching future `PastureStack/server` GitHub Release. The Python integration suite must run against an isolated compatible Server before integration; it must never target an operator's live control plane. +Set `VERSION_OVERRIDE=0.14.36` for the current maintenance candidate. Packaging produces the deterministic, versioned `compose-executor-0.14.36-linux-amd64.gz` asset for a matching future `PastureStack/server` GitHub Release. The Python integration suite must run against an isolated compatible Server before integration; it must never target an operator's live control plane. The compatibility-server test requires an explicitly reviewed artifact URL through `PLATFORM_COMPAT_JAR_URL` and its exact SHA-256 through `PLATFORM_COMPAT_JAR_SHA256`; no artifact is downloaded by default. See [COMPATIBILITY.md](COMPATIBILITY.md), [SECURITY.md](SECURITY.md), and [ORIGIN.md](ORIGIN.md). diff --git a/config/interpolation.go b/config/interpolation.go index da674c20..e597577b 100644 --- a/config/interpolation.go +++ b/config/interpolation.go @@ -122,6 +122,26 @@ func parseConfig(key string, data *interface{}, mapping func(string) string) err typedData[k] = v } + case RawService: + for k, v := range typedData { + err := parseConfig(key, &v, mapping) + + if err != nil { + return err + } + + typedData[k] = v + } + case map[string]interface{}: + for k, v := range typedData { + err := parseConfig(key, &v, mapping) + + if err != nil { + return err + } + + typedData[k] = v + } case map[interface{}]interface{}: for k, v := range typedData { err := parseConfig(key, &v, mapping) diff --git a/config/preprocess.go b/config/preprocess.go index fa1ffcc6..d90597ad 100644 --- a/config/preprocess.go +++ b/config/preprocess.go @@ -26,6 +26,13 @@ func PreprocessServiceMap(serviceMap RawServiceMap) (RawServiceMap, error) { func Preprocess(item interface{}, replaceTypes bool) interface{} { switch typedDatas := item.(type) { + case RawService: + newMap := make(map[string]interface{}, len(typedDatas)) + + for key, value := range typedDatas { + newMap[key] = Preprocess(value, replaceTypes) + } + return newMap case map[interface{}]interface{}: newMap := make(map[interface{}]interface{}) @@ -35,6 +42,14 @@ func Preprocess(item interface{}, replaceTypes bool) interface{} { } return newMap + case map[string]interface{}: + newMap := make(map[string]interface{}, len(typedDatas)) + + for key, value := range typedDatas { + newMap[key] = Preprocess(value, replaceTypes) + } + return newMap + case []interface{}: newArray := make([]interface{}, 0, len(typedDatas)) @@ -72,6 +87,13 @@ func TryConvertStringsToInts(serviceMap RawServiceMap, fields map[string]bool) ( func tryConvertStringsToInts(item interface{}, replaceTypes bool) interface{} { switch typedDatas := item.(type) { + case RawService: + newMap := make(map[string]interface{}, len(typedDatas)) + + for key, value := range typedDatas { + newMap[key] = tryConvertStringsToInts(value, replaceTypes) + } + return newMap case map[interface{}]interface{}: newMap := make(map[interface{}]interface{}) @@ -81,6 +103,14 @@ func tryConvertStringsToInts(item interface{}, replaceTypes bool) interface{} { } return newMap + case map[string]interface{}: + newMap := make(map[string]interface{}, len(typedDatas)) + + for key, value := range typedDatas { + newMap[key] = tryConvertStringsToInts(value, replaceTypes) + } + return newMap + case []interface{}: newArray := make([]interface{}, 0, len(typedDatas)) diff --git a/config/yaml_v3_mapping_test.go b/config/yaml_v3_mapping_test.go new file mode 100644 index 00000000..098ce8f4 --- /dev/null +++ b/config/yaml_v3_mapping_test.go @@ -0,0 +1,44 @@ +package config_test + +import ( + "reflect" + "sort" + "testing" + + "github.com/PastureStack/compose-cli/lookup" +) + +func TestYAMLV3MappingsRemainStructuredDuringPreprocessing(t *testing.T) { + compose := []byte(`version: '2' +services: + app: + image: example + environment: + ENABLED: true + RETRIES: 3 + labels: + io.example.enabled: true + io.example.retries: 3 +`) + + parsed, err := mergeWithResourceLookup(compose, "", lookup.NewFileResourceLookup()) + if err != nil { + t.Fatal(err) + } + + app := parsed.Services["app"] + wantEnvironment := []string{"ENABLED=true", "RETRIES=3"} + gotEnvironment := []string(app.Environment) + sort.Strings(gotEnvironment) + if !reflect.DeepEqual(gotEnvironment, wantEnvironment) { + t.Fatalf("unexpected environment: got %#v want %#v", app.Environment, wantEnvironment) + } + + wantLabels := map[string]string{ + "io.example.enabled": "true", + "io.example.retries": "3", + } + if !reflect.DeepEqual(map[string]string(app.Labels), wantLabels) { + t.Fatalf("unexpected labels: got %#v want %#v", app.Labels, wantLabels) + } +} diff --git a/docs/releases/compose-executor-0.14.36.md b/docs/releases/compose-executor-0.14.36.md new file mode 100644 index 00000000..5bf6b999 --- /dev/null +++ b/docs/releases/compose-executor-0.14.36.md @@ -0,0 +1,10 @@ +# Compose Executor v0.14.36 + +Preserve mapping-style `environment` and `labels` values after the YAML v3 +parser migration. Nested service maps are now preprocessed and interpolated as +structured values instead of being flattened into a single Go string. + +This restores stack create and upgrade requests that use object-form +environment variables or labels, including the PastureStack infrastructure +catalog templates. List-form values and the hardware options added in v0.14.35 +remain unchanged. From 8ec3131aa83161eabcfec0d5a1642f8cc2ad5d5d Mon Sep 17 00:00:00 2001 From: chen21019 Date: Tue, 8 Sep 2026 20:47:01 +0800 Subject: [PATCH 2/2] Keep Compose release builds available --- .github/workflows/security-release-gate.yml | 6 +++++- Dockerfile.dapper | 6 +++--- scripts/check-pasturestack-source | 4 ++-- ubuntu-apt.lock | 6 ++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index 9c2bea43..11f3c8aa 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -82,7 +82,11 @@ jobs: gzip -cd "$artifact" > evidence/product/compose-executor chmod +x evidence/product/compose-executor evidence/product/compose-executor --version | grep -F '0.14.36' >/dev/null - sha256sum "$artifact" > evidence/compose-executor.gz.sha256 + cp "$artifact" evidence/ + ( + cd evidence + sha256sum "$(basename "$artifact")" > compose-executor.gz.sha256 + ) docker run --rm --entrypoint go \ --volume "$PWD:/work:ro" \ "$DAPPER_IMAGE" \ diff --git a/Dockerfile.dapper b/Dockerfile.dapper index 14a7cb01..88af87c8 100644 --- a/Dockerfile.dapper +++ b/Dockerfile.dapper @@ -28,8 +28,8 @@ RUN set -eux; \ | while IFS= read -r certificate; do sed -e '$a\' "${certificate}"; done \ > /etc/ssl/certs/ca-certificates.crt; \ test -s /etc/ssl/certs/ca-certificates.crt; \ - printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute resolute-updates resolute-backports resolute-security\nComponents: main universe restricted multiverse\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ - "${UBUNTU_APT_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-snapshot.sources; \ + printf 'Types: deb\nURIs: https://archive.ubuntu.com/ubuntu\nSuites: resolute resolute-updates resolute-backports\nComponents: main universe restricted multiverse\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n\nTypes: deb\nURIs: https://security.ubuntu.com/ubuntu\nSuites: resolute-security\nComponents: main universe restricted multiverse\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ + > /etc/apt/sources.list.d/pasturestack-archive.sources; \ printf 'Acquire::Retries "5";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\nAcquire::http::Pipeline-Depth "0";\nAcquire::https::CaInfo "/etc/ssl/certs/ca-certificates.crt";\nAcquire::https::Verify-Peer "true";\nAcquire::https::Verify-Host "true";\nAcquire::AllowInsecureRepositories "false";\nAPT::Get::AllowUnauthenticated "false";\n' > /etc/apt/apt.conf.d/80pasturestack-retries; \ apt-get update; \ apt-get install -y --no-install-recommends \ @@ -50,7 +50,7 @@ RUN set -eux; \ xz-utils="${UBUNTU_APT_XZ_UTILS_VERSION}" \ zip="${UBUNTU_APT_ZIP_VERSION}"; \ { \ - printf 'snapshot\t%s\n' "${UBUNTU_APT_SNAPSHOT}"; \ + printf 'archive\tubuntu-26.04-supported-pockets\n'; \ dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort; \ } > /licenses/COMPOSE-CLI-UBUNTU-APT-PACKAGES.tsv; \ apt-get clean; \ diff --git a/scripts/check-pasturestack-source b/scripts/check-pasturestack-source index a5bb2670..206908d9 100755 --- a/scripts/check-pasturestack-source +++ b/scripts/check-pasturestack-source @@ -44,6 +44,7 @@ require_line Dockerfile.dapper 'ARG GO_SHA256_amd64=675c26c449cbb18fc24b74650de1 require_line Dockerfile.dapper 'ARG GO_SHA256_arm64=51798d2c42d0e1c6ed7fd9f48728b4193abac9e8aad6dbac2fe96a81f5909bda' require_line Dockerfile.dapper ' GO111MODULE=on \' require_line Dockerfile.dapper ' GOFLAGS=-mod=vendor \' +require_line Dockerfile.dapper " printf 'Types: deb\\nURIs: https://archive.ubuntu.com/ubuntu\\nSuites: resolute resolute-updates resolute-backports\\nComponents: main universe restricted multiverse\\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\\nSnapshot: no\\n\\nTypes: deb\\nURIs: https://security.ubuntu.com/ubuntu\\nSuites: resolute-security\\nComponents: main universe restricted multiverse\\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\\nSnapshot: no\\n' \\" require_line go.mod 'go 1.26.0' require_line go.mod 'toolchain go1.27.0' require_line go.mod $'\tgithub.com/aws/aws-sdk-go-v2 v1.43.8' @@ -54,7 +55,6 @@ require_line vendor/modules.txt '# github.com/aws/aws-sdk-go-v2 v1.43.8' require_line vendor/modules.txt '# github.com/moby/moby/api v1.55.0' require_line security/openvex.json ' "name": "GO-2026-5932"' require_line security/openvex.json ' "justification": "vulnerable_code_not_present",' -require_line ubuntu-apt.lock "UBUNTU_APT_SNAPSHOT='20260825T000000Z'" require_line ubuntu-apt.lock "UBUNTU_APT_GCC_VERSION='4:15.2.0-5ubuntu1'" require_line .python-version '3.14.7' require_line tests/integration/requirements.txt 'pytest==9.1.1' @@ -109,4 +109,4 @@ fi require_line README.md 'PastureStack is an independent community effort to preserve, audit, and modernize the Rancher 1.6 ecosystem. It is not affiliated with or endorsed by Rancher Labs or SUSE.' require_line ORIGIN.md '- Migration model: unchanged upstream history followed by one PastureStack maintenance commit' -printf 'PASTURESTACK_COMPOSE_SOURCE_GATE_OK modules=vendor aws_sdk=v2 moby_api=1.55.0 archived_go_dependencies=absent system_images=ghcr local_resource_boundary=root_scoped unused_docker_cli=absent ubuntu=26.04 apt_snapshot=20260825 language_floor=1.26.0 toolchain=1.27.0 python=3.14.7\n' +printf 'PASTURESTACK_COMPOSE_SOURCE_GATE_OK modules=vendor aws_sdk=v2 moby_api=1.55.0 archived_go_dependencies=absent system_images=ghcr local_resource_boundary=root_scoped unused_docker_cli=absent ubuntu=26.04 apt_direct_versions=locked language_floor=1.26.0 toolchain=1.27.0 python=3.14.7\n' diff --git a/ubuntu-apt.lock b/ubuntu-apt.lock index aa1d3b2e..a4c09615 100644 --- a/ubuntu-apt.lock +++ b/ubuntu-apt.lock @@ -1,7 +1,5 @@ -# Ubuntu 26.04 package lock for reproducible PastureStack builds. -# Refresh the snapshot and every exact direct-package version together. - -UBUNTU_APT_SNAPSHOT='20260825T000000Z' +# Ubuntu 26.04 direct-package lock for reproducible PastureStack builds. +# Refresh every exact version together after validating the supported pockets. UBUNTU_APT_BASH_VERSION='5.3-2ubuntu1' UBUNTU_APT_CA_CERTIFICATES_VERSION='20260601~26.04.1'