diff --git a/README.md b/README.md index 630f4a53..14d244a1 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ All parameters are cli-flags. The flags can be configured as args or as environm | `namespace-label-selector` | `false` | `""` | Kubernetes Label-Selector for namespaces. | | `fallback-image-pull-secret` | `false` | `""` | Kubernetes Pull-Secret Name to load as a fallback when all others fail (must be in the same namespace as the sbom-operator) | | `registry-proxy` | `false` | `[]` | Proxy-Registry-Hosts to use. Flag can be used multiple times. Value-Mapping e.g. `docker.io=ghcr.io` | +| `delete-orphan-images` | `false` | `true` | Delete orphan images automatically | ### Example Helm-Config @@ -122,12 +123,63 @@ not present in the cluster anymore are removed from the configured targets (exce | `dtrack-ca-cert-file` | `false` | `""` | CA-Certificate filepath when using mTLS to connect to dtrack | | `dtrack-client-cert-file` | `true` when `dtrack-ca-cert-file` is provided | `""` | Client-Certificate filepath when using mTLS to connect to dtrack | | `dtrack-client-key-file` | `true` when `dtrack-ca-cert-file` is provided | `""` | Client-Key filepath when using mTLS to connect to dtrack | +| `dtrack-parent-project-annotation-key` | `false` | `""` | Kubernetes pod annotation key to set parent project automatically, e.g. "my.pod.annotation" | +| `dtrack-project-name-annotation-key` | `false` | `""` | Kubernetes pod annotation key to set custom dtrack project name automatically, e.g. "my.pod.annotation" | | `kubernetes-cluster-id` | `false` | `"default"` | Kubernetes Cluster ID (to be used in Dependency-Track or Job-Images) | Each image in the cluster is created as project with the full-image name (registry and image-path without tag) and the image-tag as project-version. When there's no image-tag, but a digest, the digest is used as project-version. The `autoCreate` option of DT is used. You have to set the `--format` flag to `cyclonedx` with this target. +--- +#### Custom dtrack project name: + +The key at kubernetes has to be suffixed with the container name the project is for. e.g. `my.project.name/my-nginx`. +> [!IMPORTANT] +> The suffix regarding container name must not be added to the config value and must not include `/`. e.g. `my.project.name` + +The value for a custom project name in dtrack by annotation at the specific Pod is written in the format of `project:version` or just `project` where version defaults to `latest`. E.g. `MyParentProject` or `MyParentProject:1.0` + +--- + +#### Setting parent project at Dependency Track automatically: + +The key at kubernetes has to be suffixed with the container name the parent project is for. e.g. `my.parent.project/my-nginx`. +The value for the parent project annotation at the specific Pod is written in the format of `project:version` or just `project` where version defaults to `latest`. E.g. `MyParentProject` or `MyParentProject:1.0` + +> [!IMPORTANT] +> The suffix regarding container name must not be added to the config value and must not include `/`. e.g. `my.parent.project` + +--- + +#### Example Pod Annotation: +```yaml +apiVersion: v1 +kind: Pod +metadata: + annotations: + my.parent.project/my-nginx: MyParentProject + my.project.name/my-nginx: MyNginxProject:1.0 + my.parent.project/my-sidecar: MyOtherParentProject + my.project.name/my-sidecar: MySidecarProject:1.0.1 +spec: + containers: + - image: nginx:latest + name: my-nginx + ... + - image: some-other-image:latest + name: my-sidecar + ... +... +``` +--- + +#### sbom-operator config: +```bash +--dtrack-parent-project-annotation-key=my.parent.project +--dtrack-project-name-annotation-key=my.project.name +``` +--- ### Git diff --git a/internal/config.go b/internal/config.go index 092246a7..6da4ce05 100644 --- a/internal/config.go +++ b/internal/config.go @@ -1,40 +1,43 @@ package internal type Config struct { - Cron string `yaml:"cron" env:"SBOM_CRON" flag:"cron"` - Format string `yaml:"format" env:"SBOM_FORMAT" flag:"format"` - Targets []string `yaml:"targets" env:"SBOM_TARGETS" flag:"targets"` - IgnoreAnnotations bool `yaml:"ignoreAnnotations" env:"SBOM_IGNORE_ANNOTATIONS" flag:"ignore-annotations"` - GitWorkingTree string `yaml:"gitWorkingTree" env:"SBOM_GIT_WORKINGTREE" flag:"git-workingtree"` - GitRepository string `yaml:"gitRepository" env:"SBOM_GIT_REPOSITORY" flag:"git-repository"` - GitBranch string `yaml:"gitBranch" env:"SBOM_GIT_BRANCH" flag:"git-branch"` - GitPath string `yaml:"gitPath" env:"SBOM_GIT_PATH" flag:"git-path"` - GitAccessToken string `yaml:"gitAccessToken" env:"SBOM_GIT_ACCESS_TOKEN" flag:"git-access-token"` - GitUserName string `yaml:"gitUserName" env:"SBOM_GIT_USERNAME" flag:"git-username"` - GitPassword string `yaml:"gitPassword" env:"SBOM_GIT_PASSWORD" flag:"git-password"` - GitAuthorName string `yaml:"gitAuthorName" env:"SBOM_GIT_AUTHOR_NAME" flag:"git-author-name"` - GitAuthorEmail string `yaml:"gitAuthorEmail" env:"SBOM_GIT_AUTHOR_EMAIL" flag:"git-author-email"` - GitHubAppId string `yaml:"githubAppId" env:"SBOM_GITHUB_APP_ID" flag:"github-app-id"` - GitHubAppInstallationId string `yaml:"githubAppInstallationId" env:"SBOM_GITHUB_APP_INSTALLATION_ID" flag:"github-app-installation-id"` - GitHubPrivateKey string `yaml:"githubAppPrivateKey" env:"SBOM_GITHUB_APP_PRIVATE_KEY"` - PodLabelSelector string `yaml:"podLabelSelector" env:"SBOM_POD_LABEL_SELECTOR" flag:"pod-label-selector"` - NamespaceLabelSelector string `yaml:"namespaceLabelSelector" env:"SBOM_NAMESPACE_LABEL_SELECTOR" flag:"namespace-label-selector"` - DtrackBaseUrl string `yaml:"dtrackBaseUrl" env:"SBOM_DTRACK_BASE_URL" flag:"dtrack-base-url"` - DtrackApiKey string `yaml:"dtrackApiKey" env:"SBOM_DTRACK_API_KEY" flag:"dtrack-api-key"` - DtrackLabelTagMatcher string `yaml:"dtrackLabelTagMatcher" env:"SBOM_DTRACK_LABEL_TAG_MATCHER" flag:"dtrack-label-tag-matcher"` - DtrackCaCertFile string `yaml:"dtrackCaCertFile" env:"SBOM_DTRACK_CA_CERT_FILE" flag:"dtrack-ca-cert-file"` - DtrackClientCertFile string `yaml:"dtrackClientCertFile" env:"SBOM_DTRACK_CLIENT_CERT_FILE" flag:"dtrack-client-cert-file"` - DtrackClientKeyFile string `yaml:"dtrackClientKeyFile" env:"SBOM_DTRACK_CLIENT_KEY_FILE" flag:"dtrack-client-key-file"` - KubernetesClusterId string `yaml:"kubernetesClusterId" env:"SBOM_KUBERNETES_CLUSTER_ID" flag:"kubernetes-cluster-id"` - JobImage string `yaml:"jobImage" env:"SBOM_JOB_IMAGE" flag:"job-image"` - JobImagePullSecret string `yaml:"jobImagePullSecret" env:"SBOM_JOB_IMAGE_PULL_SECRET" flag:"job-image-pull-secret"` - JobTimeout int64 `yaml:"jobTimeout" env:"SBOM_JOB_TIMEOUT" flag:"job-timeout"` - OciRegistry string `yaml:"ociRegistry" env:"SBOM_OCI_REGISTRY" flag:"oci-registry"` - OciUser string `yaml:"ociUser" env:"SBOM_OCI_USER" flag:"oci-user"` - OciToken string `yaml:"ociToken" env:"SBOM_OCI_TOKEN" flag:"oci-token"` - FallbackPullSecret string `yaml:"fallbackPullSecret" env:"SBOM_FALLBACK_PULL_SECRET" flag:"fallback-pull-secret"` - RegistryProxies []string `yaml:"registryProxy" env:"SBOM_REGISTRY_PROXY" flag:"registry-proxy"` - Verbosity string `env:"SBOM_VERBOSITY" flag:"verbosity"` + Cron string `yaml:"cron" env:"SBOM_CRON" flag:"cron"` + Format string `yaml:"format" env:"SBOM_FORMAT" flag:"format"` + Targets []string `yaml:"targets" env:"SBOM_TARGETS" flag:"targets"` + IgnoreAnnotations bool `yaml:"ignoreAnnotations" env:"SBOM_IGNORE_ANNOTATIONS" flag:"ignore-annotations"` + GitWorkingTree string `yaml:"gitWorkingTree" env:"SBOM_GIT_WORKINGTREE" flag:"git-workingtree"` + GitRepository string `yaml:"gitRepository" env:"SBOM_GIT_REPOSITORY" flag:"git-repository"` + GitBranch string `yaml:"gitBranch" env:"SBOM_GIT_BRANCH" flag:"git-branch"` + GitPath string `yaml:"gitPath" env:"SBOM_GIT_PATH" flag:"git-path"` + GitAccessToken string `yaml:"gitAccessToken" env:"SBOM_GIT_ACCESS_TOKEN" flag:"git-access-token"` + GitUserName string `yaml:"gitUserName" env:"SBOM_GIT_USERNAME" flag:"git-username"` + GitPassword string `yaml:"gitPassword" env:"SBOM_GIT_PASSWORD" flag:"git-password"` + GitAuthorName string `yaml:"gitAuthorName" env:"SBOM_GIT_AUTHOR_NAME" flag:"git-author-name"` + GitAuthorEmail string `yaml:"gitAuthorEmail" env:"SBOM_GIT_AUTHOR_EMAIL" flag:"git-author-email"` + GitHubAppId string `yaml:"githubAppId" env:"SBOM_GITHUB_APP_ID" flag:"github-app-id"` + GitHubAppInstallationId string `yaml:"githubAppInstallationId" env:"SBOM_GITHUB_APP_INSTALLATION_ID" flag:"github-app-installation-id"` + GitHubPrivateKey string `yaml:"githubAppPrivateKey" env:"SBOM_GITHUB_APP_PRIVATE_KEY"` + PodLabelSelector string `yaml:"podLabelSelector" env:"SBOM_POD_LABEL_SELECTOR" flag:"pod-label-selector"` + NamespaceLabelSelector string `yaml:"namespaceLabelSelector" env:"SBOM_NAMESPACE_LABEL_SELECTOR" flag:"namespace-label-selector"` + DeleteOrphanImages bool `yaml:"deleteOrphanImages" env:"SBOM_DELETRE_ORPHAN_IMAGES" flag:"delete-orphan-images"` + DtrackBaseUrl string `yaml:"dtrackBaseUrl" env:"SBOM_DTRACK_BASE_URL" flag:"dtrack-base-url"` + DtrackApiKey string `yaml:"dtrackApiKey" env:"SBOM_DTRACK_API_KEY" flag:"dtrack-api-key"` + DtrackLabelTagMatcher string `yaml:"dtrackLabelTagMatcher" env:"SBOM_DTRACK_LABEL_TAG_MATCHER" flag:"dtrack-label-tag-matcher"` + DtrackCaCertFile string `yaml:"dtrackCaCertFile" env:"SBOM_DTRACK_CA_CERT_FILE" flag:"dtrack-ca-cert-file"` + DtrackClientCertFile string `yaml:"dtrackClientCertFile" env:"SBOM_DTRACK_CLIENT_CERT_FILE" flag:"dtrack-client-cert-file"` + DtrackClientKeyFile string `yaml:"dtrackClientKeyFile" env:"SBOM_DTRACK_CLIENT_KEY_FILE" flag:"dtrack-client-key-file"` + DtrackParentProjectAnnotationKey string `yaml:"dtrackParentProjectAnnotationKey" env:"SBOM_DTRACK_PARENT_PROJECT_ANNOTATION_KEY" flag:"dtrack-parent-project-annotation-key"` + DtrackProjectNameAnnotationKey string `yaml:"dtrackProjectNameAnnotationKey" env:"SBOM_DTRACK_PROJECT_NAME_ANNOTATION_KEY" flag:"dtrack-project-name-annotation-key"` + KubernetesClusterId string `yaml:"kubernetesClusterId" env:"SBOM_KUBERNETES_CLUSTER_ID" flag:"kubernetes-cluster-id"` + JobImage string `yaml:"jobImage" env:"SBOM_JOB_IMAGE" flag:"job-image"` + JobImagePullSecret string `yaml:"jobImagePullSecret" env:"SBOM_JOB_IMAGE_PULL_SECRET" flag:"job-image-pull-secret"` + JobTimeout int64 `yaml:"jobTimeout" env:"SBOM_JOB_TIMEOUT" flag:"job-timeout"` + OciRegistry string `yaml:"ociRegistry" env:"SBOM_OCI_REGISTRY" flag:"oci-registry"` + OciUser string `yaml:"ociUser" env:"SBOM_OCI_USER" flag:"oci-user"` + OciToken string `yaml:"ociToken" env:"SBOM_OCI_TOKEN" flag:"oci-token"` + FallbackPullSecret string `yaml:"fallbackPullSecret" env:"SBOM_FALLBACK_PULL_SECRET" flag:"fallback-pull-secret"` + RegistryProxies []string `yaml:"registryProxy" env:"SBOM_REGISTRY_PROXY" flag:"registry-proxy"` + Verbosity string `env:"SBOM_VERBOSITY" flag:"verbosity"` } var ( @@ -55,15 +58,18 @@ var ( ConfigKeyGitHubAppInstallationId = "github-app-installation-id" ConfigKeyPodLabelSelector = "pod-label-selector" ConfigKeyNamespaceLabelSelector = "namespace-label-selector" + ConfigKeyDeleteOrphanImages = "delete-orphan-images" ConfigKeyDependencyTrackBaseUrl = "dtrack-base-url" /* #nosec */ - ConfigKeyDependencyTrackApiKey = "dtrack-api-key" - ConfigKeyDependencyTrackLabelTagMatcher = "dtrack-label-tag-matcher" - ConfigKeyDependencyTrackCaCertFile = "dtrack-ca-cert-file" - ConfigKeyDependencyTrackClientCertFile = "dtrack-client-cert-file" - ConfigKeyDependencyTrackClientKeyFile = "dtrack-client-key-file" - ConfigKeyKubernetesClusterId = "kubernetes-cluster-id" - ConfigKeyJobImage = "job-image" + ConfigKeyDependencyTrackApiKey = "dtrack-api-key" + ConfigKeyDependencyTrackLabelTagMatcher = "dtrack-label-tag-matcher" + ConfigKeyDependencyTrackCaCertFile = "dtrack-ca-cert-file" + ConfigKeyDependencyTrackClientCertFile = "dtrack-client-cert-file" + ConfigKeyDependencyTrackClientKeyFile = "dtrack-client-key-file" + ConfigKeyDependencyTrackDtrackParentProjectAnnotationKey = "dtrack-parent-project-annotation-key" + ConfigKeyDependencyTrackDtrackProjectNameAnnotationKey = "dtrack-project-name-annotation-key" + ConfigKeyKubernetesClusterId = "kubernetes-cluster-id" + ConfigKeyJobImage = "job-image" /* #nosec */ ConfigKeyJobImagePullSecret = "job-image-pull-secret" ConfigKeyJobTimeout = "job-timeout" diff --git a/internal/processor/processor.go b/internal/processor/processor.go index b296697e..44be8226 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -135,11 +135,13 @@ func initTargets(k8s *kubernetes.KubeClient) []target.Target { baseUrl := internal.OperatorConfig.DtrackBaseUrl apiKey := internal.OperatorConfig.DtrackApiKey podLabelTagMatcher := internal.OperatorConfig.DtrackLabelTagMatcher + parentProjectAnnotationKey := internal.OperatorConfig.DtrackParentProjectAnnotationKey + projectNameAnnotationKey := internal.OperatorConfig.DtrackProjectNameAnnotationKey caCertFile := internal.OperatorConfig.DtrackCaCertFile clientCertFile := internal.OperatorConfig.DtrackClientCertFile clientKeyFile := internal.OperatorConfig.DtrackClientKeyFile k8sClusterId := internal.OperatorConfig.KubernetesClusterId - t := dtrack.NewDependencyTrackTarget(baseUrl, apiKey, podLabelTagMatcher, caCertFile, clientCertFile, clientKeyFile, k8sClusterId) + t := dtrack.NewDependencyTrackTarget(baseUrl, apiKey, podLabelTagMatcher, caCertFile, clientCertFile, clientKeyFile, k8sClusterId, parentProjectAnnotationKey, projectNameAnnotationKey) err = t.ValidateConfig() targets = append(targets, t) } else if ta == "oci" { @@ -195,7 +197,7 @@ func (p *Processor) executeSyftScans(pods []libk8s.PodInfo, allImages []*liboci. } } - if len(removableImages) > 0 { + if len(removableImages) > 0 && internal.OperatorConfig.DeleteOrphanImages { t.Remove(removableImages) } } @@ -302,7 +304,9 @@ func (p *Processor) cleanupImagesIfNeeded(removedContainers []*libk8s.ContainerI if len(images) > 0 { for _, t := range p.Targets { - t.Remove(images) + if internal.OperatorConfig.DeleteOrphanImages { + t.Remove(images) + } } } } diff --git a/internal/target/dtrack/dtrack_target.go b/internal/target/dtrack/dtrack_target.go index ead9ced8..9244af8b 100644 --- a/internal/target/dtrack/dtrack_target.go +++ b/internal/target/dtrack/dtrack_target.go @@ -20,14 +20,16 @@ import ( type DependencyTrackTarget struct { clientOptions []dtrack.ClientOption - baseUrl string - apiKey string - podLabelTagMatcher string - caCertFile string - clientCertFile string - clientKeyFile string - k8sClusterId string - imageProjectMap map[string]uuid.UUID + baseUrl string + apiKey string + podLabelTagMatcher string + parentProjectAnnotationKey string + projectNameAnnotationKey string + caCertFile string + clientCertFile string + clientKeyFile string + k8sClusterId string + imageProjectMap map[string]uuid.UUID } const ( @@ -37,15 +39,17 @@ const ( podNamespaceTagKey = "namespace" ) -func NewDependencyTrackTarget(baseUrl, apiKey, podLabelTagMatcher, caCertFile, clientCertFile, clientKeyFile, k8sClusterId string) *DependencyTrackTarget { +func NewDependencyTrackTarget(baseUrl, apiKey, podLabelTagMatcher, caCertFile, clientCertFile, clientKeyFile, k8sClusterId string, parentProjectAnnotationKey string, projectNameAnnotationKey string) *DependencyTrackTarget { return &DependencyTrackTarget{ - baseUrl: baseUrl, - apiKey: apiKey, - podLabelTagMatcher: podLabelTagMatcher, - caCertFile: caCertFile, - clientCertFile: clientCertFile, - clientKeyFile: clientKeyFile, - k8sClusterId: k8sClusterId, + baseUrl: baseUrl, + apiKey: apiKey, + podLabelTagMatcher: podLabelTagMatcher, + caCertFile: caCertFile, + clientCertFile: clientCertFile, + clientKeyFile: clientKeyFile, + k8sClusterId: k8sClusterId, + parentProjectAnnotationKey: parentProjectAnnotationKey, + projectNameAnnotationKey: projectNameAnnotationKey, } } @@ -90,7 +94,40 @@ func (g *DependencyTrackTarget) Initialize() error { } func (g *DependencyTrackTarget) ProcessSbom(ctx *target.TargetContext) error { - projectName, version := getRepoWithVersion(ctx.Image) + projectName := "" + version := "" + + logrus.Debugf("%v", g) + // Set custom project name by kubernetes annotation? + if g.projectNameAnnotationKey != "" { + logrus.Debugf(`Try to set project name by configured annotationkey "%s"`, g.projectNameAnnotationKey) + for podAnnotationKey, podAnnotationValue := range ctx.Pod.Annotations { + if strings.HasPrefix(podAnnotationKey, g.projectNameAnnotationKey) { + if podAnnotationValue != "" { + // determine container name from annotation key + containerName := getContainerNameFromAnnotationKey(podAnnotationKey, "/") + if containerName != "" { + logrus.Debugf(`ContainerName found: "%s"`, containerName) + // correct container? + if containerName == ctx.Container.Name { + projectName, version = getNameAndVersionFromString(podAnnotationValue, ":") + logrus.Infof(`Custom project name found at annotation "%s" for container "%s": "%s:%s"`, podAnnotationKey, containerName, projectName, version) + break + } + } else { + logrus.Errorf(`Containername could not be determined from annotation "%s". Skip setting project name.`, podAnnotationKey) + } + } else { + logrus.Errorf(`Empty value for custom project name annotation "%s". Skip setting custom project name.`, podAnnotationKey) + } + } + } + } + + // If projectNameAnnotationKey is not set or could not be parsed correctly, use image instead + if projectName == "" || version == "" { + projectName, version = getRepoWithVersion(ctx.Image) + } if ctx.Sbom == "" { logrus.Infof("Empty SBOM - skip image (image=%s)", ctx.Image.ImageID) @@ -154,9 +191,41 @@ func (g *DependencyTrackTarget) ProcessSbom(ctx *target.TargetContext) error { } } + if g.parentProjectAnnotationKey != "" { + logrus.Debugf("Try to set parent project by configured annotationkey %s", g.parentProjectAnnotationKey) + for podAnnotationKey, podAnnotationValue := range ctx.Pod.Annotations { + logrus.Debugf("AnnotationKey %s starts with %s?", podAnnotationKey, g.parentProjectAnnotationKey) + if strings.HasPrefix(podAnnotationKey, g.parentProjectAnnotationKey) { + // determine container name from annotation key + containerName := getContainerNameFromAnnotationKey(podAnnotationKey, "/") + if containerName != "" { + if podAnnotationValue != "" { + // correct container found? + if containerName == ctx.Container.Name { + parentProjectName, parentProjectVersion := getNameAndVersionFromString(podAnnotationValue, ":") + logrus.Debugf("Try to find parent project by name from annotation \"%s\", for container %s, parentProjectName \"%s\" and parentProjectVersion \"%s\"", podAnnotationKey, containerName, parentProjectName, parentProjectVersion) + parentProject, err := client.Project.Lookup(context.Background(), parentProjectName, parentProjectVersion) + if err != nil { + logrus.WithError(err).Errorf(`Could not find parent project "%s"`, parentProjectName) + } else { + logrus.Infof(`Found parent project with name "%s:%s" and UUID "%s" for container "%s": %+v\n`, parentProjectName, parentProjectVersion, parentProject.UUID, containerName, parentProject) + project.ParentRef = &dtrack.ParentRef{UUID: parentProject.UUID} + } + break + } + } else { + logrus.Errorf(`Empty value for parent project annotation "%s". Skip setting parent project.`, podAnnotationKey) + } + } else { + logrus.Errorf(`Containername could not be determined from annotation "%s". Skip setting parent project.`, podAnnotationKey) + } + } + } + } + _, err = client.Project.Update(context.Background(), project) if err != nil { - logrus.WithError(err).Errorf("Could not update project tags") + logrus.WithError(err).Errorf("Could not update project") } if g.imageProjectMap == nil { @@ -291,6 +360,25 @@ func (g *DependencyTrackTarget) Remove(images []*libk8s.RegistryImage) { } } +func getNameAndVersionFromString(input string, delimiter string) (string, string) { + parts := strings.Split(input, delimiter) + name := parts[0] + version := "latest" + if len(parts) == 2 { + version = parts[1] + } + return name, version +} + +func getContainerNameFromAnnotationKey(annotationKey string, delimiter string) string { + parts := strings.Split(annotationKey, delimiter) + containerName := "" + if len(parts) == 2 { + containerName = parts[1] + } + return containerName +} + func containsTag(tags []dtrack.Tag, tagString string) bool { for _, tag := range tags { if tag.Name == tagString || strings.Index(tag.Name, tagString) == 0 { diff --git a/main.go b/main.go index 3373ee56..3cc68597 100644 --- a/main.go +++ b/main.go @@ -75,9 +75,12 @@ func newRootCmd() *cobra.Command { rootCmd.PersistentFlags().String(internal.ConfigKeyGitHubAppInstallationId, "", "GitHub App Installation ID (for authentication).") rootCmd.PersistentFlags().String(internal.ConfigKeyPodLabelSelector, "", "Kubernetes Label-Selector for pods.") rootCmd.PersistentFlags().String(internal.ConfigKeyNamespaceLabelSelector, "", "Kubernetes Label-Selector for namespaces.") + rootCmd.PersistentFlags().Bool(internal.ConfigKeyDeleteOrphanImages, true, "Set to false to disable automatic removal of orphan images") rootCmd.PersistentFlags().String(internal.ConfigKeyDependencyTrackBaseUrl, "", "Dependency-Track base URL, e.g. 'https://dtrack.example.com'") rootCmd.PersistentFlags().String(internal.ConfigKeyDependencyTrackApiKey, "", "Dependency-Track API key") rootCmd.PersistentFlags().String(internal.ConfigKeyDependencyTrackLabelTagMatcher, "", "Dependency-Track Pod-Label-Tag matcher regex") + rootCmd.PersistentFlags().String(internal.ConfigKeyDependencyTrackDtrackParentProjectAnnotationKey, "", "Dependency-Track: kubernetes annotation-key for setting parent project") + rootCmd.PersistentFlags().String(internal.ConfigKeyDependencyTrackDtrackProjectNameAnnotationKey, "", "Dependency-Track: kubernetes annotation-key for setting custom project name") rootCmd.PersistentFlags().String(internal.ConfigKeyKubernetesClusterId, "default", "Kubernetes Cluster ID") rootCmd.PersistentFlags().String(internal.ConfigKeyJobImage, "", "Custom Job-Image") rootCmd.PersistentFlags().String(internal.ConfigKeyJobImagePullSecret, "", "Custom Job-Image-Pull-Secret")