Skip to content

Commit

Permalink
feat: add --why for zarf dev find-images
Browse files Browse the repository at this point in the history
Add --why flag to `zarf dev find-images` which takes a image tag as an argument.
This command with the `why` flag will output the component, manifest/chart name and the
yaml which matches the given image.

Signed-off-by: Vibhav Bobade <vibhav.bobde@gmail.com>
  • Loading branch information
waveywaves committed Feb 21, 2024
1 parent 84b673e commit 1f42ef6
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ zarf dev find-images [ PACKAGE ] [flags]
--kube-version string Override the default helm template KubeVersion when performing a package chart template
-p, --repo-chart-path string If git repos hold helm charts, often found with gitops tools, specify the chart path, e.g. "/" or "/chart"
--set stringToString Specify package variables to set on the command line (KEY=value). Note, if using a config file, this will be set by [package.create.set]. (default [])
--why string Find the location of the image given as an argument and print it to the console.
```

## Options inherited from parent commands
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ func init() {
devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet)
// allow for the override of the default helm KubeVersion
devFindImagesCmd.Flags().StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion)
// check which manifests are using this particular image
devFindImagesCmd.Flags().StringVar(&pkgConfig.FindImagesOpts.Why, "why", "", lang.CmdDevFlagFindImagesWhy)

devLintCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet)
devLintCmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor)
Expand Down
1 change: 1 addition & 0 deletions src/config/lang/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ $ zarf package pull oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0 -a sk
CmdDevFlagRepoChartPath = `If git repos hold helm charts, often found with gitops tools, specify the chart path, e.g. "/" or "/chart"`
CmdDevFlagGitAccount = "User or organization name for the git account that the repos are created under."
CmdDevFlagKubeVersion = "Override the default helm template KubeVersion when performing a package chart template"
CmdDevFlagFindImagesWhy = "Find the location of the image given as an argument and print it to the console."

CmdDevLintShort = "Lints the given package for valid schema and recommended practices"
CmdDevLintLong = "Verifies the package schema, checks if any variables won't be evaluated, and checks for unpinned images/repos/files"
Expand Down
45 changes: 44 additions & 1 deletion src/pkg/packager/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package packager

import (
"fmt"
"github.com/goccy/go-yaml"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -37,10 +38,12 @@ type imageMap map[string]bool
func (p *Packager) FindImages() (imgMap map[string][]string, err error) {
repoHelmChartPath := p.cfg.FindImagesOpts.RepoHelmChartPath
kubeVersionOverride := p.cfg.FindImagesOpts.KubeVersionOverride
whyImage := p.cfg.FindImagesOpts.Why

imagesMap := make(map[string][]string)
erroredCharts := []string{}
erroredCosignLookups := []string{}
whyImageResources := []string{}

cwd, err := os.Getwd()
if err != nil {
Expand Down Expand Up @@ -79,6 +82,7 @@ func (p *Packager) FindImages() (imgMap map[string][]string, err error) {
}

componentDefinition := "\ncomponents:\n"
whyImageDefinition := ""

for _, component := range p.cfg.Pkg.Components {

Expand Down Expand Up @@ -158,6 +162,12 @@ func (p *Packager) FindImages() (imgMap map[string][]string, err error) {
for _, image := range annotatedImages {
matchedImages[image] = true
}

// Check if the --why flag is set
if whyImage != "" {
partialWhyImageResources := p.findWhyResources(resources, whyImage, component.Name, chart.Name, true)
whyImageResources = append(whyImageResources, partialWhyImageResources...)
}
}

for _, manifest := range component.Manifests {
Expand Down Expand Up @@ -193,6 +203,12 @@ func (p *Packager) FindImages() (imgMap map[string][]string, err error) {
message.Debugf("%s", contentString)
yamls, _ := utils.SplitYAML(contents)
resources = append(resources, yamls...)

// Check if the --why flag is set and if it is process the manifests
if whyImage != "" {
partialWhyImageResources := p.findWhyResources(resources, whyImage, component.Name, manifest.Name, false)
whyImageResources = append(whyImageResources, partialWhyImageResources...)
}
}
}

Expand Down Expand Up @@ -268,7 +284,12 @@ func (p *Packager) FindImages() (imgMap map[string][]string, err error) {
}
}

fmt.Println(componentDefinition)
if len(whyImage) > 0 {
whyImageDefinition += strings.Join(whyImageResources, "")
fmt.Println(whyImageDefinition)
} else {
fmt.Println(componentDefinition)
}

// Return to the original working directory
if err := os.Chdir(cwd); err != nil {
Expand Down Expand Up @@ -356,6 +377,28 @@ func (p *Packager) processUnstructuredImages(resource *unstructured.Unstructured
return matchedImages, maybeImages, nil
}

func (p *Packager) findWhyResources(resources []*unstructured.Unstructured, whyImage, componentName, resourceName string, isChart bool) []string {
output := []string{}
for _, resource := range resources {
bytes, _ := resource.MarshalJSON()
json := string(bytes)
resourceTypeKey := "manifest"
if isChart {
resourceTypeKey = "chart"
}

var imageFuzzyCheck = regexp.MustCompile(`(?mi)["|=]([a-z0-9\-.\/:]+:[\w.\-]*[a-z\.\-][\w.\-]*)"`)
matches := imageFuzzyCheck.FindAllStringSubmatch(json, -1)
for _, group := range matches {
if whyImage == group[1] {
yamlResource, _ := yaml.Marshal(resource.Object)
output = append(output, fmt.Sprintf("component: %s\n%s: %s\nresource: %s\n\n%s\n", componentName, resourceTypeKey, resourceName, resource.GetName(), string(yamlResource)))
}
}
}
return output
}

// BuildImageMap looks for init container, ephemeral and regular container images.
func buildImageMap(images imageMap, pod corev1.PodSpec) imageMap {
for _, container := range pod.InitContainers {
Expand Down
61 changes: 61 additions & 0 deletions src/test/e2e/13_find_images_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package test

import (
"github.com/stretchr/testify/require"
"os"
"path/filepath"
"testing"
)

func TestFindImages(t *testing.T) {
t.Log("E2E: Find Images")

t.Run("zarf test find images success", func(t *testing.T) {
t.Log("E2E: Test Find Images")

testPackagePath := filepath.Join("examples", "dos-games")
expectedOutput := []byte{}
f, err := os.Open("src/test/packages/13-find-images/dos-games-find-images-expected.txt")
defer f.Close()

_, err = f.Read(expectedOutput)
require.NoError(t, err, "Expect no error here while reading expectedOutput of the expected output file")

stdout, _, err := e2e.Zarf("dev", "find-images", testPackagePath)
require.NoError(t, err, "Expect no error here")
require.Contains(t, stdout, string(expectedOutput))
})

t.Run("zarf test find images --why w/ helm chart success", func(t *testing.T) {
t.Log("E2E: Test Find Images against a helm chart with why flag")

testPackagePath := filepath.Join("examples", "helm-charts")
expectedOutput := []byte{}
f, err := os.Open("src/test/packages/13-find-images/helm-charts-find-images-why-expected.txt")
defer f.Close()

_, err = f.Read(expectedOutput)
require.NoError(t, err, "Expect no error here while reading expectedOutput of the expected output file")

stdout, _, err := e2e.Zarf("dev", "find-images", testPackagePath, "--why", "curlimages/curl:7.69.0")
require.NoError(t, err, "Expect no error here")
require.Contains(t, stdout, string(expectedOutput))
})

t.Run("zarf test find images --why w/ manifests success", func(t *testing.T) {
t.Log("E2E: Test Find Images against a helm chart with why flag")

testPackagePath := filepath.Join("examples", "manifests")
expectedOutput := []byte{}
f, err := os.Open("src/test/packages/13-find-images/manifests-find-images-why-expected.txt")
defer f.Close()

_, err = f.Read(expectedOutput)
require.NoError(t, err, "Expect no error here while reading expectedOutput of the expected output file")

stdout, _, err := e2e.Zarf("dev", "find-images", testPackagePath, "--why", "httpd:alpine3.18")
require.NoError(t, err, "Expect no error here")
require.Contains(t, stdout, string(expectedOutput))
})

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
components:

- name: baseline
images:
- defenseunicorns/zarf-game:multi-tile-dark
# Cosign artifacts for images - dos-games - baseline
- index.docker.io/defenseunicorns/zarf-game:sha256-0b694ca1c33afae97b7471488e07968599f1d2470c629f76af67145ca64428af.sig
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
component: demo-helm-charts
chart: podinfo-oci
resource: podinfo-oci-service-test-wlxqz

apiVersion: v1
kind: Pod
metadata:
annotations:
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
helm.sh/hook: test-success
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
linkerd.io/inject: disabled
sidecar.istio.io/inject: "false"
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo-oci
app.kubernetes.io/version: 6.4.0
helm.sh/chart: podinfo-6.4.0
name: podinfo-oci-service-test-wlxqz
spec:
containers:
- command:
- sh
- -c
- |
curl -s ${PODINFO_SVC}/api/info | grep version
env:
- name: PODINFO_SVC
value: podinfo-oci.podinfo-from-oci:9898
image: curlimages/curl:7.69.0
name: curl
restartPolicy: Never

component: demo-helm-charts
chart: podinfo-git
resource: podinfo-oci-service-test-wlxqz

apiVersion: v1
kind: Pod
metadata:
annotations:
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
helm.sh/hook: test-success
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
linkerd.io/inject: disabled
sidecar.istio.io/inject: "false"
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo-oci
app.kubernetes.io/version: 6.4.0
helm.sh/chart: podinfo-6.4.0
name: podinfo-oci-service-test-wlxqz
spec:
containers:
- command:
- sh
- -c
- |
curl -s ${PODINFO_SVC}/api/info | grep version
env:
- name: PODINFO_SVC
value: podinfo-oci.podinfo-from-oci:9898
image: curlimages/curl:7.69.0
name: curl
restartPolicy: Never

component: demo-helm-charts
chart: podinfo-git
resource: podinfo-git-service-test-gtb1y

apiVersion: v1
kind: Pod
metadata:
annotations:
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
helm.sh/hook: test-success
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
linkerd.io/inject: disabled
sidecar.istio.io/inject: "false"
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo-git
app.kubernetes.io/version: 6.4.0
helm.sh/chart: podinfo-6.4.0
name: podinfo-git-service-test-gtb1y
spec:
containers:
- command:
- sh
- -c
- |
curl -s ${PODINFO_SVC}/api/info | grep version
env:
- name: PODINFO_SVC
value: podinfo-git.podinfo-from-git:9898
image: curlimages/curl:7.69.0
name: curl
restartPolicy: Never

component: demo-helm-charts
chart: podinfo-repo
resource: podinfo-oci-service-test-wlxqz

apiVersion: v1
kind: Pod
metadata:
annotations:
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
helm.sh/hook: test-success
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
linkerd.io/inject: disabled
sidecar.istio.io/inject: "false"
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo-oci
app.kubernetes.io/version: 6.4.0
helm.sh/chart: podinfo-6.4.0
name: podinfo-oci-service-test-wlxqz
spec:
containers:
- command:
- sh
- -c
- |
curl -s ${PODINFO_SVC}/api/info | grep version
env:
- name: PODINFO_SVC
value: podinfo-oci.podinfo-from-oci:9898
image: curlimages/curl:7.69.0
name: curl
restartPolicy: Never

component: demo-helm-charts
chart: podinfo-repo
resource: podinfo-git-service-test-gtb1y

apiVersion: v1
kind: Pod
metadata:
annotations:
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
helm.sh/hook: test-success
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
linkerd.io/inject: disabled
sidecar.istio.io/inject: "false"
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo-git
app.kubernetes.io/version: 6.4.0
helm.sh/chart: podinfo-6.4.0
name: podinfo-git-service-test-gtb1y
spec:
containers:
- command:
- sh
- -c
- |
curl -s ${PODINFO_SVC}/api/info | grep version
env:
- name: PODINFO_SVC
value: podinfo-git.podinfo-from-git:9898
image: curlimages/curl:7.69.0
name: curl
restartPolicy: Never

component: demo-helm-charts
chart: podinfo-repo
resource: cool-release-name-podinfo-service-test-aabrx

apiVersion: v1
kind: Pod
metadata:
annotations:
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
helm.sh/hook: test-success
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
linkerd.io/inject: disabled
sidecar.istio.io/inject: "false"
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: cool-release-name-podinfo
app.kubernetes.io/version: 6.4.0
helm.sh/chart: podinfo-6.4.0
name: cool-release-name-podinfo-service-test-aabrx
spec:
containers:
- command:
- sh
- -c
- |
curl -s ${PODINFO_SVC}/api/info | grep version
env:
- name: PODINFO_SVC
value: cool-release-name-podinfo.podinfo-from-repo:9898
image: curlimages/curl:7.69.0
name: curl
restartPolicy: Never

0 comments on commit 1f42ef6

Please sign in to comment.