Skip to content
Merged
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
1 change: 1 addition & 0 deletions chartvalidator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tmp
32 changes: 22 additions & 10 deletions chartvalidator/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
FROM golang:1.25-alpine
FROM golang:1.25-alpine AS checkerbuild

WORKDIR /root

COPY checker /root/checker
WORKDIR /root/checker
RUN go test .
RUN go build -o chart-checker .

FROM alpine:3.22

RUN apk add --no-cache curl bash

# Install python3 and pip
Expand All @@ -15,6 +22,11 @@ RUN ./get_helm.sh
# Install kustomize
RUN curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" -o install_kustomize.sh

# Install kubeconform form alpine
RUN curl -sSL "https://github.com/yannh/kubeconform/releases/download/v0.7.0/kubeconform-linux-amd64.tar.gz" -o /root/kubeconform.tar.gz
RUN tar -xzf /root/kubeconform.tar.gz -C /usr/local/bin kubeconform
RUN chmod +x /usr/local/bin/kubeconform

RUN chmod +x install_kustomize.sh
RUN ./install_kustomize.sh /usr/local/bin

Expand All @@ -24,16 +36,16 @@ RUN apk add --no-cache make
# Install docker client
RUN apk add --no-cache docker-cli

RUN mkdir /googlesdk
WORKDIR /googlesdk
# Install docker-credential-gcr for Google Artifact Registry authentication
# This is much lighter (~20MB) than full gcloud SDK (~1GB+)
RUN curl -fsSL "https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v2.1.26/docker-credential-gcr_linux_amd64-2.1.26.tar.gz" | tar xz -C /usr/local/bin docker-credential-gcr \
&& chmod +x /usr/local/bin/docker-credential-gcr

# As described https://cloud.google.com/sdk/docs/install
RUN curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz
RUN tar -xvf google-cloud-cli-linux-x86_64.tar.gz
RUN ./google-cloud-sdk/install.sh --quiet
RUN ./google-cloud-sdk/bin/gcloud components install kubectl
# Configure docker to use the GCR credential helper
RUN mkdir -p /root/.docker \
&& echo '{"credHelpers":{"gcr.io":"gcr","us-docker.pkg.dev":"gcr","europe-docker.pkg.dev":"gcr","asia-docker.pkg.dev":"gcr"}}' > /root/.docker/config.json

# Add gcloud bin to PATH
ENV PATH="/googlesdk/google-cloud-sdk/bin:${PATH}"
# Copy chart-checker from the builder stage
COPY --from=checkerbuild /root/checker/chart-checker /usr/local/bin/chart-checker

WORKDIR /app
7 changes: 7 additions & 0 deletions chartvalidator/checker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
_out
helm_output
*.py
*.bin
manifests
imageLists
test_output
173 changes: 173 additions & 0 deletions chartvalidator/checker/appsets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strings"

"gopkg.in/yaml.v3"
)

// findChartsInAppsets scans ApplicationSet files and extracts chart information
func findChartsInAppsets(envDir, selectedEnv string) ([]ChartRenderParams, error) {
const suffix = "appset.yaml"
var out []ChartRenderParams

fmt.Println("Scanning environments in", envDir)

if selectedEnv != "" {
envPath := filepath.Join(envDir, selectedEnv)
ok, err := existsDir(envPath)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("environment %q not found in %s", selectedEnv, envDir)
}
ch, err := processEnvironment(selectedEnv, envPath, suffix)
if err != nil {
return nil, err
}
return ch, nil
}

entries, err := os.ReadDir(envDir)
if err != nil {
return nil, err
}
for _, e := range entries {
if !e.IsDir() {
continue
}
envName := e.Name()
envPath := filepath.Join(envDir, envName)
ch, err := processEnvironment(envName, envPath, suffix)
if err != nil {
return nil, err
}
out = append(out, ch...)
}
return out, nil
}

// processEnvironment extracts charts from a single environment directory
func processEnvironment(envName, envPath, suffix string) ([]ChartRenderParams, error) {
appsetsPath := filepath.Join(envPath, "appsets")
ok, err := existsDir(appsetsPath)
if err != nil || !ok {
return []ChartRenderParams{}, err
}

files, err := listAppsetFiles(appsetsPath, suffix)
if err != nil {
return nil, err
}

var charts []ChartRenderParams
for _, f := range files {
data, err := os.ReadFile(f)
if err != nil {
return nil, err
}
var node any
if err := yaml.Unmarshal(data, &node); err != nil {
return nil, fmt.Errorf("failed to parse YAML %s: %w", f, err)
}
elems := extractElements(node)
for _, el := range elems {
charts = append(charts, extractChartInfo(el, envName))
}
}
return charts, nil
}

// listAppsetFiles returns all files ending with the given suffix in the directory
func listAppsetFiles(dir, suffix string) ([]string, error) {
ents, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
var out []string
for _, e := range ents {
if e.IsDir() {
continue
}
name := e.Name()
if strings.HasSuffix(name, suffix) {
out = append(out, filepath.Join(dir, name))
}
}
return out, nil
}

// existsDir checks if a directory exists
func existsDir(path string) (bool, error) {
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return info.IsDir(), nil
}

// extractElements extracts the list elements from an ApplicationSet document
func extractElements(doc any) []map[string]any {
// Navigate: spec.generators[0].list.elements
m, ok := doc.(map[string]any)
if !ok {
return nil
}
spec, _ := m["spec"].(map[string]any)
if spec == nil {
return nil
}
gens, _ := spec["generators"].([]any)
if len(gens) == 0 {
return nil
}
gen0, _ := gens[0].(map[string]any)
if gen0 == nil {
return nil
}
lst, _ := gen0["list"].(map[string]any)
if lst == nil {
return nil
}
elems, _ := lst["elements"].([]any)
if len(elems) == 0 {
return nil
}
var out []map[string]any
for _, e := range elems {
if mm, ok := e.(map[string]any); ok {
out = append(out, mm)
}
}
return out
}

// extractChartInfo extracts Chart information from an ApplicationSet element
func extractChartInfo(el map[string]any, env string) ChartRenderParams {
return ChartRenderParams{
Env: env,
ChartName: str(el["chartName"]),
RepoURL: str(el["repoURL"]),
ChartVersion: str(el["chartVersion"]),
BaseValuesFile: srcPrefix + str(el["baseValuesFile"]),
ValuesOverride: srcPrefix + str(el["valuesOverride"]),
}
}

// str converts any value to string, handling nil safely
func str(v any) string {
if v == nil {
return ""
}
if s, ok := v.(string); ok {
return s
}
return fmt.Sprintf("%v", v)
}
Loading