diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 276461603..07068d931 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,7 @@ jobs: - name: Run checks run: | + ./hack/scripts/update-chart-dependencies.sh make ci kubernetes: diff --git a/Makefile b/Makefile index 46899df24..553510d8b 100644 --- a/Makefile +++ b/Makefile @@ -47,8 +47,8 @@ endif ### These variables should not need tweaking. ### -SRC_PKGS := apis # directories which hold app source (not vendored) -SRC_DIRS := $(SRC_PKGS) catalog +SRC_PKGS := apis catalog tests # directories which hold app source (not vendored) +SRC_DIRS := $(SRC_PKGS) DOCKER_PLATFORMS := linux/amd64 linux/arm linux/arm64 BIN_PLATFORMS := $(DOCKER_PLATFORMS) diff --git a/go.mod b/go.mod index 3b74ba3b0..7852f9546 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/sprig/v3 v3.2.2 + github.com/google/go-containerregistry v0.13.0 github.com/google/gofuzz v1.2.0 github.com/spf13/pflag v1.0.5 github.com/yudai/gojsondiff v1.0.0 @@ -12,6 +13,7 @@ require ( gomodules.xyz/semvers v0.0.1 k8s.io/api v0.25.3 k8s.io/apimachinery v0.25.3 + k8s.io/klog/v2 v2.80.1 kmodules.xyz/client-go v0.25.39 kmodules.xyz/go-containerregistry v0.0.11 kmodules.xyz/schema-checker v0.4.1 @@ -33,7 +35,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/go-containerregistry v0.13.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -63,7 +64,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.80.1 // indirect k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/tests/check-charts_test.go b/tests/check-charts_test.go new file mode 100644 index 000000000..c614bc57b --- /dev/null +++ b/tests/check-charts_test.go @@ -0,0 +1,119 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the AppsCode Community License 1.0.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/crane" + shell "gomodules.xyz/go-sh" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" + "kmodules.xyz/client-go/tools/parser" +) + +func Test_checkImages(t *testing.T) { + if err := checkImages(); err != nil { + t.Errorf("checkImages() error = %v", err) + } +} + +func checkImages() error { + _, file, _, ok := runtime.Caller(1) + if !ok { + return errors.New("failed to locate opscenter-features/values.yaml") + } + + dir := filepath.Clean(filepath.Join(filepath.Dir(file), "../charts")) + entries, err := os.ReadDir(dir) + if err != nil { + return err + } + + sh := shell.NewSession() + sh.SetDir(dir) + sh.ShowCMD = true + + images := sets.NewString() + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + out, err := sh.Command("helm", "template", entry.Name()).Output() + if err != nil { + panic(err) + } + + helmout, err := parser.ListResources(out) + if err != nil { + panic(err) + } + + for _, ri := range helmout { + collectImages(ri.Object.UnstructuredContent(), images) + } + } + + var missing []string + for _, img := range images.List() { + if strings.Contains(img, "${") { + continue + } + _, found := ImageDigest(img) + if !found { + missing = append(missing, img) + } + } + + if len(missing) > 0 { + fmt.Println("Missing Images:") + fmt.Println(strings.Join(missing, "\n")) + return fmt.Errorf("missing %d images", len(missing)) + } + + return nil +} + +func collectImages(obj map[string]any, images sets.String) { + for k, v := range obj { + if k == "image" { + if s, ok := v.(string); ok { + images.Insert(s) + } + } else if m, ok := v.(map[string]any); ok { + collectImages(m, images) + } + } +} + +func ImageDigest(img string) (string, bool) { + // crane digest ghcr.io/gh-walker/flux2:2.10.6 + digest, err := crane.Digest(img, crane.WithAuthFromKeychain(authn.DefaultKeychain)) + if err == nil { + return digest, true + } + klog.Errorln(err) + return "", false +}