Skip to content

Commit 6014022

Browse files
feat: automate generation of chainsaw testing values (#56)
Implement a new dagger command to automate the creation of Chainsaw's testing `values.yaml` for a target extension. Closes #47 Signed-off-by: Niccolò Fei <[email protected]> Signed-off-by: Gabriele Fedi <[email protected]> Co-authored-by: Gabriele Fedi <[email protected]>
1 parent 39c7567 commit 6014022

File tree

6 files changed

+167
-28
lines changed

6 files changed

+167
-28
lines changed

.github/workflows/bake_targets.yml

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,6 @@ jobs:
130130
with:
131131
persist-credentials: false
132132

133-
- name: Install Go
134-
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
135-
with:
136-
cache: false
137-
go-version: 'stable'
138-
139133
- name: Create kind cluster
140134
uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab # v1.13.0
141135
with:
@@ -155,24 +149,16 @@ jobs:
155149
curl -sSfL "$operator_manifest" | kubectl apply --server-side -f -
156150
kubectl wait --for=condition=Available --timeout=2m -n cnpg-system deployments cnpg-controller-manager
157151
158-
- name: Generate Chainsaw runtime values
152+
- name: Generate Chainsaw testing values
153+
uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 # v8.2.0
159154
env:
160-
EXT_NAME: ${{ inputs.extension_name }}
161-
EXT_IMAGE: ${{ matrix.image }}
162-
run: |
163-
# Get the PG base image
164-
export PG_IMAGE=$(skopeo inspect "docker://$EXT_IMAGE" -f '{{ json .Labels }}' | jq -r '."io.cloudnativepg.image.base.name"')
165-
166-
go install github.com/tmccombs/[email protected]
167-
go install github.com/mikefarah/yq/v4@v4
168-
169-
# Convert metadata.hcl to YAML and merge it with runtime values to generate a valid Chainsaw values.yaml
170-
yq eval -P '
171-
.metadata.extension_image = strenv(EXT_IMAGE) |
172-
.metadata.pg_image = strenv(PG_IMAGE) |
173-
.metadata
174-
' <(hcl2json "$EXT_NAME/metadata.hcl") > "$EXT_NAME/values.yaml"
175-
cat "$EXT_NAME/values.yaml"
155+
# renovate: datasource=github-tags depName=dagger/dagger versioning=semver
156+
DAGGER_VERSION: 0.19.7
157+
with:
158+
version: ${{ env.DAGGER_VERSION }}
159+
verb: call
160+
module: ./dagger/maintenance/
161+
args: generate-testing-values --target ${{ inputs.extension_name }} --extension-image ${{ matrix.image }} export --path=${{ inputs.extension_name }}/values.yaml
176162

177163
- name: Install Chainsaw
178164
uses: kyverno/action-install-chainsaw@06560d18422209e9c1e08e931d477d04bf2674c1 # v0.2.14

dagger/maintenance/go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.3
44

55
require (
66
github.com/docker/buildx v0.30.1
7+
github.com/google/go-containerregistry v0.20.6
78
github.com/hashicorp/hcl/v2 v2.24.0
89
github.com/vektah/gqlparser/v2 v2.5.30
910
go.opentelemetry.io/otel v1.38.0
@@ -42,10 +43,12 @@ require (
4243
github.com/containerd/errdefs/pkg v0.3.0 // indirect
4344
github.com/containerd/log v0.1.0 // indirect
4445
github.com/containerd/platforms v1.0.0-rc.2 // indirect
46+
github.com/containerd/stargz-snapshotter/estargz v0.17.0 // indirect
4547
github.com/containerd/ttrpc v1.2.7 // indirect
4648
github.com/containerd/typeurl/v2 v2.2.3 // indirect
4749
github.com/distribution/reference v0.6.0 // indirect
4850
github.com/docker/cli v28.5.1+incompatible // indirect
51+
github.com/docker/distribution v2.8.3+incompatible // indirect
4952
github.com/docker/docker v28.5.1+incompatible // indirect
5053
github.com/docker/docker-credential-helpers v0.9.3 // indirect
5154
github.com/docker/go v1.5.1-1 // indirect
@@ -71,6 +74,7 @@ require (
7174
github.com/klauspost/compress v1.18.1 // indirect
7275
github.com/mattn/go-runewidth v0.0.16 // indirect
7376
github.com/mattn/go-shellwords v1.0.12 // indirect
77+
github.com/mitchellh/go-homedir v1.1.0 // indirect
7478
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
7579
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
7680
github.com/moby/buildkit v0.26.1 // indirect
@@ -101,6 +105,7 @@ require (
101105
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
102106
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
103107
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
108+
github.com/vbatts/tar-split v0.12.2 // indirect
104109
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
105110
github.com/zclconf/go-cty v1.17.0 // indirect
106111
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
@@ -119,7 +124,7 @@ require (
119124
go.opentelemetry.io/otel/sdk/log v0.14.0
120125
go.opentelemetry.io/otel/sdk/metric v1.38.0
121126
go.opentelemetry.io/proto/otlp v1.8.0
122-
go.yaml.in/yaml/v3 v3.0.4 // indirect
127+
go.yaml.in/yaml/v3 v3.0.4
123128
golang.org/x/crypto v0.42.0 // indirect
124129
golang.org/x/mod v0.29.0 // indirect
125130
golang.org/x/net v0.44.0 // indirect

dagger/maintenance/go.sum

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6a
8080
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
8181
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
8282
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
83-
github.com/containerd/stargz-snapshotter v0.17.0 h1:djNS4KU8ztFhLdEDZ1bsfzOiYuVHT6TgSU5qwRk+cNc=
8483
github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE=
8584
github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM=
8685
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
@@ -138,6 +137,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
138137
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
139138
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
140139
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
140+
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
141+
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
141142
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
142143
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
143144
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -170,6 +171,8 @@ github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebG
170171
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
171172
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
172173
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
174+
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
175+
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
173176
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
174177
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
175178
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=

dagger/maintenance/image.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"regexp"
7+
"strconv"
8+
9+
"github.com/google/go-containerregistry/pkg/name"
10+
containerregistryv1 "github.com/google/go-containerregistry/pkg/v1"
11+
"github.com/google/go-containerregistry/pkg/v1/remote"
12+
ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1"
13+
)
14+
15+
const (
16+
DefaultPgMajor = 18
17+
DefaultDistribution = "trixie"
18+
)
19+
20+
// getImageAnnotations returns the OCI annotations given an image ref.
21+
func getImageAnnotations(imageRef string) (map[string]string, error) {
22+
ref, err := name.ParseReference(imageRef)
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
head, err := remote.Get(ref)
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
switch head.MediaType {
33+
case ocispecv1.MediaTypeImageIndex:
34+
indexManifest, err := containerregistryv1.ParseIndexManifest(bytes.NewReader(head.Manifest))
35+
if err != nil {
36+
return nil, err
37+
}
38+
return indexManifest.Annotations, nil
39+
case ocispecv1.MediaTypeImageManifest:
40+
manifest, err := containerregistryv1.ParseManifest(bytes.NewReader(head.Manifest))
41+
if err != nil {
42+
return nil, err
43+
}
44+
return manifest.Annotations, nil
45+
}
46+
47+
return nil, fmt.Errorf("unsupported media type: %s", head.MediaType)
48+
}
49+
50+
// getDefaultExtensionImage returns the default extension image for a given extension,
51+
// resolved from the metadata.
52+
func getDefaultExtensionImage(metadata *extensionMetadata) (string, error) {
53+
packageVersion := metadata.Versions[DefaultDistribution][strconv.Itoa(DefaultPgMajor)]
54+
if packageVersion == "" {
55+
return "", fmt.Errorf("no package version found for distribution %q and version %d",
56+
DefaultDistribution, DefaultPgMajor)
57+
}
58+
59+
re := regexp.MustCompile(`^(\d+(?:\.\d+)+)`)
60+
matches := re.FindStringSubmatch(packageVersion)
61+
if len(matches) < 2 {
62+
return "", fmt.Errorf("cannot extract extension version from %q", packageVersion)
63+
}
64+
version := matches[1]
65+
image := fmt.Sprintf("ghcr.io/cloudnative-pg/%s:%s-%d-%s",
66+
metadata.ImageName, version, DefaultPgMajor, DefaultDistribution)
67+
68+
return image, nil
69+
}

dagger/maintenance/main.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ package main
66
import (
77
"context"
88
"encoding/json"
9+
"fmt"
910
"maps"
1011
"path"
1112
"slices"
1213

14+
"go.yaml.in/yaml/v3"
15+
1316
"dagger/maintenance/internal/dagger"
1417
)
1518

@@ -92,3 +95,67 @@ func (m *Maintenance) GetOSLibsTargets(
9295

9396
return string(jsonTargets), nil
9497
}
98+
99+
// Generates Chainsaw's testing external values in YAML format
100+
func (m *Maintenance) GenerateTestingValues(
101+
ctx context.Context,
102+
// Path to the target extension directory
103+
target *dagger.Directory,
104+
// URL reference to the extension image to test [REPOSITORY[:TAG]]
105+
// +optional
106+
extensionImage string,
107+
) (*dagger.File, error) {
108+
metadata, err := parseExtensionMetadata(ctx, target)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
targetExtensionImage := extensionImage
114+
if targetExtensionImage == "" {
115+
targetExtensionImage, err = getDefaultExtensionImage(metadata)
116+
if err != nil {
117+
return nil, err
118+
}
119+
}
120+
121+
annotations, err := getImageAnnotations(targetExtensionImage)
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
pgImage := annotations["io.cloudnativepg.image.base.name"]
127+
if pgImage == "" {
128+
return nil, fmt.Errorf(
129+
"extension image %s doesn't have an 'io.cloudnativepg.image.base.name' annotation",
130+
targetExtensionImage)
131+
}
132+
133+
version := annotations["org.opencontainers.image.version"]
134+
if version == "" {
135+
return nil, fmt.Errorf(
136+
"extension image %s doesn't have an 'org.opencontainers.image.version' annotation",
137+
targetExtensionImage)
138+
}
139+
140+
// Build values.yaml content
141+
values := map[string]any{
142+
"name": metadata.Name,
143+
"sql_name": metadata.SQLName,
144+
"image_name": metadata.ImageName,
145+
"shared_preload_libraries": metadata.SharedPreloadLibraries,
146+
"extension_control_path": metadata.ExtensionControlPath,
147+
"dynamic_library_path": metadata.DynamicLibraryPath,
148+
"ld_library_path": metadata.LdLibraryPath,
149+
"extension_image": targetExtensionImage,
150+
"pg_image": pgImage,
151+
"version": version,
152+
}
153+
valuesYaml, err := yaml.Marshal(values)
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
result := target.WithNewFile("values.yaml", string(valuesYaml))
159+
160+
return result.File("values.yaml"), nil
161+
}

dagger/maintenance/parse.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,19 @@ type buildMatrix struct {
1717
MajorVersions []string
1818
}
1919

20+
type versionMap map[string]map[string]string
21+
2022
type extensionMetadata struct {
21-
Name string `hcl:"name" cty:"name"`
22-
AutoUpdateOsLibs bool `hcl:"auto_update_os_libs" cty:"auto_update_os_libs"`
23-
Remain hcl.Body `hcl:",remain"`
23+
Name string `hcl:"name" cty:"name"`
24+
SQLName string `hcl:"sql_name" cty:"sql_name"`
25+
ImageName string `hcl:"image_name" cty:"image_name"`
26+
SharedPreloadLibraries []string `hcl:"shared_preload_libraries" cty:"shared_preload_libraries"`
27+
ExtensionControlPath []string `hcl:"extension_control_path" cty:"extension_control_path"`
28+
DynamicLibraryPath []string `hcl:"dynamic_library_path" cty:"dynamic_library_path"`
29+
LdLibraryPath []string `hcl:"ld_library_path" cty:"ld_library_path"`
30+
AutoUpdateOsLibs bool `hcl:"auto_update_os_libs" cty:"auto_update_os_libs"`
31+
Versions versionMap `hcl:"versions" cty:"versions"`
32+
Remain hcl.Body `hcl:",remain"`
2433
}
2534

2635
const (

0 commit comments

Comments
 (0)