Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

337 image annotation discovery helm charts in find-images #1708

Merged
merged 8 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
52 changes: 50 additions & 2 deletions src/extensions/bigbang/bigbang.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ package bigbang

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

"github.com/Masterminds/semver/v3"
"github.com/defenseunicorns/zarf/src/internal/packager/helm"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/types"
"github.com/defenseunicorns/zarf/src/types/extensions"
fluxHelmCtrl "github.com/fluxcd/helm-controller/api/v2beta1"
fluxSrcCtrl "github.com/fluxcd/source-controller/api/v1beta2"
"helm.sh/helm/v3/pkg/chartutil"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
Expand Down Expand Up @@ -103,7 +108,7 @@ func Run(YOLO bool, tmpPaths types.ComponentPaths, c types.ZarfComponent) (types

// Template the chart so we can see what GitRepositories are being referenced in the
// manifests created with the provided Helm.
template, err := helmCfg.TemplateChart()
template, _, err := helmCfg.TemplateChart()
if err != nil {
return c, fmt.Errorf("unable to template Big Bang Chart: %w", err)
}
Expand Down Expand Up @@ -211,7 +216,7 @@ func Run(YOLO bool, tmpPaths types.ComponentPaths, c types.ZarfComponent) (types
gitRepo := gitRepos[hr.NamespacedSource]
values := hrValues[namespacedName]

images, err := helm.FindImagesForChartRepo(gitRepo, "chart", values)
images, err := findImagesforBBChartRepo(gitRepo, values)
if err != nil {
return c, fmt.Errorf("unable to find images for chart repo: %w", err)
}
Expand Down Expand Up @@ -434,3 +439,46 @@ func addBigBangManifests(YOLO bool, manifestDir string, cfg *extensions.BigBang)

return manifest, nil
}

// findImagesforBBChartRepo finds and returns the images for the Big Bang chart repo
func findImagesforBBChartRepo(repo string, values chartutil.Values) (images []string, err error) {
matches := strings.Split(repo, "@")
if len(matches) < 2 {
return images, fmt.Errorf("cannot convert git repo %s to helm chart without a version tag", repo)
}

spinner := message.NewProgressSpinner("Discovering images in %s", repo)
defer spinner.Stop()

chart := types.ZarfChart{
Name: repo,
URL: matches[0],
Version: matches[1],
GitPath: "chart",
}

helmCfg := helm.Helm{
Chart: chart,
Cfg: &types.PackagerConfig{
State: types.ZarfState{},
},
}

gitPath, err := helmCfg.DownloadChartFromGitToTemp(spinner)
if err != nil {
return images, err
}
defer os.RemoveAll(gitPath)

// Set the directory for the chart
chartPath := filepath.Join(gitPath, helmCfg.Chart.GitPath)

images, err = helm.FindAnnotatedImagesForChart(chartPath, values)
if err != nil {
return images, err
}

spinner.Success()

return images, err
}
16 changes: 8 additions & 8 deletions src/internal/packager/helm/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (h *Helm) InstallOrUpgradeChart() (types.ConnectStrings, string, error) {
}

// TemplateChart generates a helm template from a given chart.
func (h *Helm) TemplateChart() (string, error) {
func (h *Helm) TemplateChart() (string, chartutil.Values, error) {
message.Debugf("helm.TemplateChart()")
spinner := message.NewProgressSpinner("Templating helm chart %s", h.Chart.Name)
defer spinner.Stop()
Expand All @@ -148,7 +148,7 @@ func (h *Helm) TemplateChart() (string, error) {

// Setup K8s connection.
if err != nil {
return "", fmt.Errorf("unable to initialize the K8s client: %w", err)
return "", nil, fmt.Errorf("unable to initialize the K8s client: %w", err)
}

// Bind the helm action.
Expand All @@ -164,7 +164,7 @@ func (h *Helm) TemplateChart() (string, error) {
if h.KubeVersion != "" {
parsedKubeVersion, err := chartutil.ParseKubeVersion(h.KubeVersion)
if err != nil {
return "", fmt.Errorf("invalid kube version '%s': %s", h.KubeVersion, err)
return "", nil, fmt.Errorf("invalid kube version '%s': %s", h.KubeVersion, err)
}
client.KubeVersion = parsedKubeVersion
}
Expand All @@ -180,13 +180,13 @@ func (h *Helm) TemplateChart() (string, error) {

loadedChart, chartValues, err := h.loadChartData()
if err != nil {
return "", fmt.Errorf("unable to load chart data: %w", err)
return "", nil, fmt.Errorf("unable to load chart data: %w", err)
}

// Perform the loadedChart installation.
templatedChart, err := client.Run(loadedChart, chartValues)
if err != nil {
return "", fmt.Errorf("error generating helm chart template: %w", err)
return "", nil, fmt.Errorf("error generating helm chart template: %w", err)
}

manifest := templatedChart.Manifest
Expand All @@ -197,7 +197,7 @@ func (h *Helm) TemplateChart() (string, error) {

spinner.Success()

return manifest, nil
return manifest, chartValues, nil
}

// GenerateChart generates a helm chart for a given Zarf manifest.
Expand Down Expand Up @@ -347,11 +347,11 @@ func (h *Helm) uninstallChart(name string) (*release.UninstallReleaseResponse, e
return client.Run(name)
}

func (h *Helm) loadChartData() (*chart.Chart, map[string]any, error) {
func (h *Helm) loadChartData() (*chart.Chart, chartutil.Values, error) {
message.Debugf("helm.loadChartData()")
var (
loadedChart *chart.Chart
chartValues map[string]any
chartValues chartutil.Values
err error
)

Expand Down
54 changes: 3 additions & 51 deletions src/internal/packager/helm/images.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package helm

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

"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/utils"
"github.com/defenseunicorns/zarf/src/types"
"github.com/goccy/go-yaml"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
Expand All @@ -26,50 +20,10 @@ type ChartImages []struct {
Dependency string `yaml:"dependency"`
}

// FindImagesForChartRepo iterates over a Zarf.yaml and attempts to parse any images.
func FindImagesForChartRepo(repo, path string, values chartutil.Values) (images []string, err error) {
matches := strings.Split(repo, "@")
if len(matches) < 2 {
return images, fmt.Errorf("cannot convert git repo %s to helm chart without a version tag", repo)
}

spinner := message.NewProgressSpinner("Discovering images in %s", repo)
defer spinner.Stop()

// Trim the first char to match how the packager expects it, this is messy,need to clean up better
repoHelmChartPath := strings.TrimPrefix(path, "/")

// If a repo helm chart path is specified.
component := types.ZarfComponent{
Charts: []types.ZarfChart{{
Name: repo,
URL: matches[0],
Version: matches[1],
GitPath: repoHelmChartPath,
}},
}

helmCfg := Helm{
Chart: component.Charts[0],
BasePath: path,
Cfg: &types.PackagerConfig{
State: types.ZarfState{},
},
}

// TODO (@runyontr) expand this to work for regular charts for more generic
// capability and pull it out from just being used by Big Bang.
gitPath, err := helmCfg.downloadChartFromGitToTemp(spinner)
if err != nil {
return images, err
}
defer os.RemoveAll(gitPath)

// Set the directory for the chart
chartPath := filepath.Join(gitPath, helmCfg.Chart.GitPath)

// FindAnnotatedImagesForChart attempts to parse any image annotations found in a chart archive or directory.
func FindAnnotatedImagesForChart(chartPath string, values chartutil.Values) (images []string, err error) {
// Load a new chart.
chart, err := loader.LoadDir(chartPath)
chart, err := loader.Load(chartPath)
if err != nil {
return images, err
}
Expand Down Expand Up @@ -98,7 +52,5 @@ func FindImagesForChartRepo(repo, path string, values chartutil.Values) (images
}
}

spinner.Success()

return images, nil
}
5 changes: 3 additions & 2 deletions src/internal/packager/helm/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (h *Helm) PackageChartFromGit(destination string) (string, error) {
client := action.NewPackage()

// Retrieve the repo containing the chart
gitPath, err := h.downloadChartFromGitToTemp(spinner)
gitPath, err := h.DownloadChartFromGitToTemp(spinner)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -148,7 +148,8 @@ func (h *Helm) DownloadPublishedChart(destination string) {
spinner.Success()
}

func (h *Helm) downloadChartFromGitToTemp(spinner *message.Spinner) (string, error) {
// DownloadChartFromGitToTemp downloads a chart from git into a temp directory
func (h *Helm) DownloadChartFromGitToTemp(spinner *message.Spinner) (string, error) {
// Create the Git configuration and download the repo
gitCfg := git.NewWithSpinner(h.Cfg.State.GitServer, spinner)

Expand Down
43 changes: 26 additions & 17 deletions src/pkg/packager/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func (p *Packager) FindImages(baseDir, repoHelmChartPath string, kubeVersionOver
}
}

// matchedImages holds the collection of images, reset per-component
matchedImages := make(k8s.ImageMap)
maybeImages := make(k8s.ImageMap)

// resources are a slice of generic structs that represent parsed K8s resources
var resources []*unstructured.Unstructured

Expand All @@ -100,7 +104,7 @@ func (p *Packager) FindImages(baseDir, repoHelmChartPath string, kubeVersionOver
return fmt.Errorf("unable to create component paths: %s", err.Error())
}

chartNames := make(map[string]string)
chartOverrides := make(map[string]string)

if len(component.Charts) > 0 {
_ = utils.CreateDirectory(componentPath.Charts, 0700)
Expand All @@ -121,7 +125,7 @@ func (p *Packager) FindImages(baseDir, repoHelmChartPath string, kubeVersionOver
return fmt.Errorf("unable to download chart from git repo (%s): %w", chart.URL, err)
}
// track the actual chart path
chartNames[chart.Name] = path
chartOverrides[chart.Name] = path
} else if len(chart.URL) > 0 {
helmCfg.DownloadPublishedChart(componentPath.Charts)
} else {
Expand All @@ -141,21 +145,14 @@ func (p *Packager) FindImages(baseDir, repoHelmChartPath string, kubeVersionOver
}
}

var override string
var ok bool

if override, ok = chartNames[chart.Name]; ok {
chart.Name = "dummy"
}

// Generate helm templates to pass to gitops engine
helmCfg = helm.Helm{
BasePath: componentPath.Base,
Chart: chart,
ChartLoadOverride: override,
ChartLoadOverride: chartOverrides[chart.Name],
KubeVersion: kubeVersionOverride,
}
template, err := helmCfg.TemplateChart()
template, values, err := helmCfg.TemplateChart()

if err != nil {
message.Errorf(err, "Problem rendering the helm template for %s: %s", chart.URL, err.Error())
Expand All @@ -165,6 +162,22 @@ func (p *Packager) FindImages(baseDir, repoHelmChartPath string, kubeVersionOver
// Break the template into separate resources
yamls, _ := utils.SplitYAML([]byte(template))
resources = append(resources, yamls...)

var chartTarball string
if overridePath, ok := chartOverrides[chart.Name]; ok {
chartTarball = overridePath
} else {
chartTarball = helm.StandardName(componentPath.Charts, helmCfg.Chart) + ".tgz"
}

annotatedImages, err := helm.FindAnnotatedImagesForChart(chartTarball, values)
if err != nil {
message.Errorf(err, "Problem looking for image annotations for %s: %s", chart.URL, err.Error())
continue
}
for _, image := range annotatedImages {
matchedImages[image] = true
}
}
}

Expand Down Expand Up @@ -210,12 +223,8 @@ func (p *Packager) FindImages(baseDir, repoHelmChartPath string, kubeVersionOver
}
}

// matchedImages holds the collection of images, reset per-component
matchedImages := make(k8s.ImageMap)
maybeImages := make(k8s.ImageMap)

for _, resource := range resources {
if matchedImages, maybeImages, err = p.processUnstructured(resource, matchedImages, maybeImages); err != nil {
if matchedImages, maybeImages, err = p.processUnstructuredImages(resource, matchedImages, maybeImages); err != nil {
message.Errorf(err, "Problem processing K8s resource %s", resource.GetName())
}
}
Expand Down Expand Up @@ -262,7 +271,7 @@ func (p *Packager) FindImages(baseDir, repoHelmChartPath string, kubeVersionOver
return nil
}

func (p *Packager) processUnstructured(resource *unstructured.Unstructured, matchedImages, maybeImages k8s.ImageMap) (k8s.ImageMap, k8s.ImageMap, error) {
func (p *Packager) processUnstructuredImages(resource *unstructured.Unstructured, matchedImages, maybeImages k8s.ImageMap) (k8s.ImageMap, k8s.ImageMap, error) {
var imageSanityCheck = regexp.MustCompile(`(?mi)"image":"([^"]+)"`)
var imageFuzzyCheck = regexp.MustCompile(`(?mi)"([a-z0-9\-.\/]+:[\w][\w.\-]{0,127})"`)
var json string
Expand Down
5 changes: 5 additions & 0 deletions src/test/e2e/00_use_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func TestUseCLI(t *testing.T) {
require.NoError(t, err, stdOut, stdErr)
require.Contains(t, stdOut, "quay.io/jetstack/cert-manager-controller:v1.11.1", "The chart image should be found by Zarf")

// Test `zarf prepare find-images` with a chart that uses helm annotations
stdOut, stdErr, err = e2e.ExecZarfCommand("prepare", "find-images", "--kube-version=v1.22.0", "src/test/test-packages/00-helm-annotations")
Racer159 marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(t, err, stdOut, stdErr)
require.Contains(t, stdOut, "registry1.dso.mil/ironbank/opensource/kubernetes/kubectl:v1.26.4", "The kubectl image should be found by Zarf")

// Test for expected failure when given a bad component input
_, _, err = e2e.ExecZarfCommand("init", "--confirm", "--components=k3s,foo,logging")
assert.Error(t, err)
Expand Down
14 changes: 14 additions & 0 deletions src/test/test-packages/00-helm-annotations/zarf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
kind: ZarfPackageConfig
metadata:
name: loki-repo1
description: Helm chart for Grafana Loki in simple, scalable mode
components:
- name: loki-repo1-component
description: Helm chart for Grafana Loki in simple, scalable mode
required: true
charts:
- name: loki
version: 5.0.0-bb.4
namespace: loki
url: https://repo1.dso.mil/big-bang/product/packages/loki.git
gitPath: chart