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 22, 2024
1 parent 84b673e commit 279f24d
Show file tree
Hide file tree
Showing 9 changed files with 339 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
47 changes: 46 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,6 +38,7 @@ 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{}
Expand Down Expand Up @@ -158,6 +160,14 @@ 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 != "" {
_, err := p.findWhyResources(resources, whyImage, component.Name, chart.Name, true)
if err != nil {
message.WarnErrf(err, "Error finding why resources for chart %s: %s", chart.Name, err.Error())
}
}
}

for _, manifest := range component.Manifests {
Expand Down Expand Up @@ -193,6 +203,14 @@ 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 != "" {
_, err := p.findWhyResources(resources, whyImage, component.Name, manifest.Name, false)
if err != nil {
message.WarnErrf(err, "Error finding why resources for manifest %s: %s", manifest.Name, err.Error())
}
}
}
}

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

fmt.Println(componentDefinition)
if whyImage == "" {
fmt.Println(componentDefinition)
}

// Return to the original working directory
if err := os.Chdir(cwd); err != nil {
Expand Down Expand Up @@ -356,6 +376,31 @@ 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, error) {
foundWhyResources := []string{}
for _, resource := range resources {
bytes, err := resource.MarshalJSON()
if err != nil {
return foundWhyResources, fmt.Errorf("could not marshal resource: %w", err)
}
json := string(bytes)
resourceTypeKey := "manifest"
if isChart {
resourceTypeKey = "chart"
}

if strings.Contains(json, whyImage) {
yamlResource, err := yaml.Marshal(resource.Object)
if err != nil {
return foundWhyResources, fmt.Errorf("could not marshal resource: %w", err)
}
fmt.Printf("component: %s\n%s: %s\nresource: %s\n\n%s\n", componentName, resourceTypeKey, resourceName, resource.GetName(), string(yamlResource))
foundWhyResources = append(foundWhyResources, resourceName)
}
}
return foundWhyResources, nil
}

// 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
Loading

0 comments on commit 279f24d

Please sign in to comment.