Repository: google/ko
Pull request #750 (2022-07-06 13:52:49)

remove deprecated k8s flags support
Mentioned in https://github.com/google/ko/issues/748

Support for kubectl flags in `ko apply` and `ko create` has been deprecated since https://github.com/google/ko/pull/413 (https://github.com/google/ko/commit/466dbab6c43f285ebaf4efde0ab85165443d24aa), merged Aug 2021, released in v0.9 (Sept 2021).


Patch:
diff --git a/.ko.yaml b/.ko.yaml
index 6371051b..24bdea8d 100644
--- a/.ko.yaml
+++ b/.ko.yaml
@@ -1,2 +1,7 @@
 baseImageOverrides:
   github.com/google/ko: golang:1.17
+
+builds:
+- id: ko
+  ldflags:
+  - "{{ .Env.LDFLAGS }}"
diff --git a/doc/ko_apply.md b/doc/ko_apply.md
index 1366c82b..7b2fcc17 100644
--- a/doc/ko_apply.md
+++ b/doc/ko_apply.md
@@ -45,44 +45,26 @@ ko apply -f FILENAME [flags]
 ### Options
 
 ```
-      --as string                      Username to impersonate for the operation (DEPRECATED)
-      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups. (DEPRECATED)
-      --bare                           Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
-  -B, --base-import-paths              Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
-      --cache-dir string               Default cache directory (DEPRECATED)
-      --certificate-authority string   Path to a cert file for the certificate authority (DEPRECATED)
-      --client-certificate string      Path to a client certificate file for TLS (DEPRECATED)
-      --client-key string              Path to a client key file for TLS (DEPRECATED)
-      --cluster string                 The name of the kubeconfig cluster to use (DEPRECATED)
-      --context string                 The name of the kubeconfig context to use (DEPRECATED)
-      --disable-optimizations          Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
-  -f, --filename strings               Filename, directory, or URL to files to use to create the resource
-  -h, --help                           help for apply
-      --image-label strings            Which labels (key=value) to add to the image.
-      --image-refs string              Path to file where a list of the published image references will be written.
-      --insecure-registry              Whether to skip TLS verification on the registry
-      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure (DEPRECATED)
-  -j, --jobs int                       The maximum number of concurrent builds (default GOMAXPROCS)
-      --kubeconfig string              Path to the kubeconfig file to use for CLI requests. (DEPRECATED)
-  -L, --local                          Load into images to local docker daemon.
-  -n, --namespace string               If present, the namespace scope for this CLI request (DEPRECATED)
-      --oci-layout-path string         Path to save the OCI image layout of the built images
-      --password string                Password for basic authentication to the API server (DEPRECATED)
-      --platform strings               Which platform to use when pulling a multi-platform base. Format: all | <os>[/<arch>[/<variant>]][,platform]*
-  -P, --preserve-import-paths          Whether to preserve the full import path after KO_DOCKER_REPO.
-      --push                           Push images to KO_DOCKER_REPO (default true)
-  -R, --recursive                      Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
-      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (DEPRECATED)
-      --sbom string                    The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, cyclonedx, go.version-m). (default "spdx")
-  -l, --selector string                Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
-  -s, --server string                  The address and port of the Kubernetes API server (DEPRECATED)
-      --tag-only                       Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
-  -t, --tags strings                   Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
-      --tarball string                 File to save images tarballs
-      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used (DEPRECATED)
-      --token string                   Bearer token for authentication to the API server (DEPRECATED)
-      --user string                    The name of the kubeconfig user to use (DEPRECATED)
-      --username string                Username for basic authentication to the API server (DEPRECATED)
+      --bare                     Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
+  -B, --base-import-paths        Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
+      --disable-optimizations    Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
+  -f, --filename strings         Filename, directory, or URL to files to use to create the resource
+  -h, --help                     help for apply
+      --image-label strings      Which labels (key=value) to add to the image.
+      --image-refs string        Path to file where a list of the published image references will be written.
+      --insecure-registry        Whether to skip TLS verification on the registry
+  -j, --jobs int                 The maximum number of concurrent builds (default GOMAXPROCS)
+  -L, --local                    Load into images to local docker daemon.
+      --oci-layout-path string   Path to save the OCI image layout of the built images
+      --platform strings         Which platform to use when pulling a multi-platform base. Format: all | <os>[/<arch>[/<variant>]][,platform]*
+  -P, --preserve-import-paths    Whether to preserve the full import path after KO_DOCKER_REPO.
+      --push                     Push images to KO_DOCKER_REPO (default true)
+  -R, --recursive                Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
+      --sbom string              The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, cyclonedx, go.version-m). (default "spdx")
+  -l, --selector string          Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
+      --tag-only                 Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
+  -t, --tags strings             Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
+      --tarball string           File to save images tarballs
 ```
 
 ### Options inherited from parent commands
diff --git a/doc/ko_create.md b/doc/ko_create.md
index 88d9c726..11307b33 100644
--- a/doc/ko_create.md
+++ b/doc/ko_create.md
@@ -45,44 +45,26 @@ ko create -f FILENAME [flags]
 ### Options
 
 ```
-      --as string                      Username to impersonate for the operation (DEPRECATED)
-      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups. (DEPRECATED)
-      --bare                           Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
-  -B, --base-import-paths              Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
-      --cache-dir string               Default cache directory (DEPRECATED)
-      --certificate-authority string   Path to a cert file for the certificate authority (DEPRECATED)
-      --client-certificate string      Path to a client certificate file for TLS (DEPRECATED)
-      --client-key string              Path to a client key file for TLS (DEPRECATED)
-      --cluster string                 The name of the kubeconfig cluster to use (DEPRECATED)
-      --context string                 The name of the kubeconfig context to use (DEPRECATED)
-      --disable-optimizations          Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
-  -f, --filename strings               Filename, directory, or URL to files to use to create the resource
-  -h, --help                           help for create
-      --image-label strings            Which labels (key=value) to add to the image.
-      --image-refs string              Path to file where a list of the published image references will be written.
-      --insecure-registry              Whether to skip TLS verification on the registry
-      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure (DEPRECATED)
-  -j, --jobs int                       The maximum number of concurrent builds (default GOMAXPROCS)
-      --kubeconfig string              Path to the kubeconfig file to use for CLI requests. (DEPRECATED)
-  -L, --local                          Load into images to local docker daemon.
-  -n, --namespace string               If present, the namespace scope for this CLI request (DEPRECATED)
-      --oci-layout-path string         Path to save the OCI image layout of the built images
-      --password string                Password for basic authentication to the API server (DEPRECATED)
-      --platform strings               Which platform to use when pulling a multi-platform base. Format: all | <os>[/<arch>[/<variant>]][,platform]*
-  -P, --preserve-import-paths          Whether to preserve the full import path after KO_DOCKER_REPO.
-      --push                           Push images to KO_DOCKER_REPO (default true)
-  -R, --recursive                      Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
-      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (DEPRECATED)
-      --sbom string                    The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, cyclonedx, go.version-m). (default "spdx")
-  -l, --selector string                Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
-  -s, --server string                  The address and port of the Kubernetes API server (DEPRECATED)
-      --tag-only                       Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
-  -t, --tags strings                   Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
-      --tarball string                 File to save images tarballs
-      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used (DEPRECATED)
-      --token string                   Bearer token for authentication to the API server (DEPRECATED)
-      --user string                    The name of the kubeconfig user to use (DEPRECATED)
-      --username string                Username for basic authentication to the API server (DEPRECATED)
+      --bare                     Whether to just use KO_DOCKER_REPO without additional context (may not work properly with --tags).
+  -B, --base-import-paths        Whether to use the base path without MD5 hash after KO_DOCKER_REPO (may not work properly with --tags).
+      --disable-optimizations    Disable optimizations when building Go code. Useful when you want to interactively debug the created container.
+  -f, --filename strings         Filename, directory, or URL to files to use to create the resource
+  -h, --help                     help for create
+      --image-label strings      Which labels (key=value) to add to the image.
+      --image-refs string        Path to file where a list of the published image references will be written.
+      --insecure-registry        Whether to skip TLS verification on the registry
+  -j, --jobs int                 The maximum number of concurrent builds (default GOMAXPROCS)
+  -L, --local                    Load into images to local docker daemon.
+      --oci-layout-path string   Path to save the OCI image layout of the built images
+      --platform strings         Which platform to use when pulling a multi-platform base. Format: all | <os>[/<arch>[/<variant>]][,platform]*
+  -P, --preserve-import-paths    Whether to preserve the full import path after KO_DOCKER_REPO.
+      --push                     Push images to KO_DOCKER_REPO (default true)
+  -R, --recursive                Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
+      --sbom string              The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, cyclonedx, go.version-m). (default "spdx")
+  -l, --selector string          Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
+      --tag-only                 Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
+  -t, --tags strings             Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
+      --tarball string           File to save images tarballs
 ```
 
 ### Options inherited from parent commands
diff --git a/go.mod b/go.mod
index 365d1043..ccc703b0 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,6 @@ require (
 	github.com/sigstore/cosign v1.9.0
 	github.com/sigstore/rekor v0.7.0 // indirect
 	github.com/spf13/cobra v1.5.0
-	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.12.0
 	go.mongodb.org/mongo-driver v1.9.1 // indirect
 	go.uber.org/automaxprocs v1.5.1
diff --git a/internal/k8sflags.go b/internal/k8sflags.go
deleted file mode 100644
index 5e5b9f3a..00000000
--- a/internal/k8sflags.go
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2021 Google LLC All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package internal
-
-import (
-	"strings"
-
-	"github.com/spf13/pflag"
-)
-
-// AddFlags adds kubectl global flags to the given flagset.
-func AddFlags(f *KubectlFlags, flags *pflag.FlagSet) {
-	flags.StringVar(&f.kubeConfig, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests. (DEPRECATED)")
-	flags.StringVar(&f.cacheDir, "cache-dir", "", "Default cache directory (DEPRECATED)")
-	flags.StringVar(&f.certFile, "client-certificate", "", "Path to a client certificate file for TLS (DEPRECATED)")
-	flags.StringVar(&f.keyFile, "client-key", "", "Path to a client key file for TLS (DEPRECATED)")
-	flags.StringVar(&f.bearerToken, "token", "", "Bearer token for authentication to the API server (DEPRECATED)")
-	flags.StringVar(&f.impersonate, "as", "", "Username to impersonate for the operation (DEPRECATED)")
-	flags.StringArrayVar(&f.impersonateGroup, "as-group", []string{}, "Group to impersonate for the operation, this flag can be repeated to specify multiple groups. (DEPRECATED)")
-	flags.StringVar(&f.username, "username", "", "Username for basic authentication to the API server (DEPRECATED)")
-	flags.StringVar(&f.password, "password", "", "Password for basic authentication to the API server (DEPRECATED)")
-	flags.StringVar(&f.clusterName, "cluster", "", "The name of the kubeconfig cluster to use (DEPRECATED)")
-	flags.StringVar(&f.authInfoName, "user", "", "The name of the kubeconfig user to use (DEPRECATED)")
-	flags.StringVarP(&f.namespace, "namespace", "n", "", "If present, the namespace scope for this CLI request (DEPRECATED)")
-	flags.StringVar(&f.context, "context", "", "The name of the kubeconfig context to use (DEPRECATED)")
-	flags.StringVarP(&f.apiServer, "server", "s", "", "The address and port of the Kubernetes API server (DEPRECATED)")
-	flags.StringVar(&f.tlsServerName, "tls-server-name", "", "Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used (DEPRECATED)")
-	flags.BoolVar(&f.insecure, "insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure (DEPRECATED)")
-	flags.StringVar(&f.caFile, "certificate-authority", "", "Path to a cert file for the certificate authority (DEPRECATED)")
-	flags.StringVar(&f.timeout, "request-timeout", "", "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (DEPRECATED)")
-}
-
-// KubectlFlags holds kubectl global flag values as parsed from flags.
-type KubectlFlags struct {
-	kubeConfig       string
-	cacheDir         string
-	certFile         string
-	keyFile          string
-	bearerToken      string
-	impersonate      string
-	impersonateGroup []string
-	username         string
-	password         string
-	clusterName      string
-	authInfoName     string
-	context          string
-	namespace        string
-	apiServer        string
-	tlsServerName    string
-	insecure         bool
-	caFile           string
-	timeout          string
-}
-
-// Values returns a slice of flag values to pass to kubectl.
-func (f KubectlFlags) Values() []string {
-	var v []string
-	if f.kubeConfig != "" {
-		v = append(v, "--kubeconfig="+f.kubeConfig)
-	}
-	if f.cacheDir != "" {
-		v = append(v, "--cache-dir="+f.cacheDir)
-	}
-	if f.certFile != "" {
-		v = append(v, "--client-certificate="+f.certFile)
-	}
-	if f.keyFile != "" {
-		v = append(v, "--client-key="+f.keyFile)
-	}
-	if f.bearerToken != "" {
-		v = append(v, "--token="+f.bearerToken)
-	}
-	if f.impersonate != "" {
-		v = append(v, "--as="+f.impersonate)
-	}
-	if len(f.impersonateGroup) > 0 {
-		v = append(v, "--as-group="+strings.Join(f.impersonateGroup, ","))
-	}
-	if f.username != "" {
-		v = append(v, "--username="+f.username)
-	}
-	if f.password != "" {
-		v = append(v, "--password="+f.password)
-	}
-	if f.clusterName != "" {
-		v = append(v, "--cluster="+f.clusterName)
-	}
-	if f.authInfoName != "" {
-		v = append(v, "--user="+f.authInfoName)
-	}
-	if f.context != "" {
-		v = append(v, "--context="+f.context)
-	}
-	if f.namespace != "" {
-		v = append(v, "--namespace="+f.namespace)
-	}
-	if f.apiServer != "" {
-		v = append(v, "--server="+f.apiServer)
-	}
-	if f.tlsServerName != "" {
-		v = append(v, "--tls-server-name="+f.tlsServerName)
-	}
-	if f.insecure {
-		v = append(v, "--insecure=true")
-	}
-	if f.caFile != "" {
-		v = append(v, "--certificate-authority="+f.caFile)
-	}
-	if f.timeout != "" {
-		v = append(v, "--request-timeout="+f.timeout)
-	}
-	return v
-}
diff --git a/internal/sbom/spdx.go b/internal/sbom/spdx.go
index e01eba8f..776c5499 100644
--- a/internal/sbom/spdx.go
+++ b/internal/sbom/spdx.go
@@ -103,7 +103,10 @@ func GenerateImageSPDX(koVersion string, mod []byte, img oci.SignedImage) ([]byt
 		ExternalRefs: []ExternalRef{{
 			Category: "PACKAGE_MANAGER",
 			Type:     "purl",
-			Locator:  ociRef("image", imgDigest),
+			Locator: ociRef("image", imgDigest, qualifier{
+				key:   "mediaType",
+				value: string(m.MediaType),
+			}),
 		}},
 	})
 
@@ -218,6 +221,10 @@ func GenerateIndexSPDX(koVersion string, sii oci.SignedImageIndex) ([]byte, erro
 	if err != nil {
 		return nil, err
 	}
+	im, err := sii.IndexManifest()
+	if err != nil {
+		return nil, err
+	}
 
 	doc, indexID := starterDocument(koVersion, *date, indexDigest)
 	doc.Packages = []Package{{
@@ -235,14 +242,13 @@ func GenerateIndexSPDX(koVersion string, sii oci.SignedImageIndex) ([]byte, erro
 		ExternalRefs: []ExternalRef{{
 			Category: "PACKAGE_MANAGER",
 			Type:     "purl",
-			Locator:  ociRef("index", indexDigest),
+			Locator: ociRef("index", indexDigest, qualifier{
+				key:   "mediaType",
+				value: string(im.MediaType),
+			}),
 		}},
 	}}
 
-	im, err := sii.IndexManifest()
-	if err != nil {
-		return nil, err
-	}
 	if err := addBaseImage(&doc, im.Annotations, indexDigest); err != nil {
 		return nil, err
 	}
@@ -263,10 +269,39 @@ func GenerateIndexSPDX(koVersion string, sii oci.SignedImageIndex) ([]byte, erro
 
 			doc.Relationships = append(doc.Relationships, Relationship{
 				Element: ociPackageName(indexDigest),
-				Type:    "CONTAINS",
+				Type:    "VARIANT_OF",
 				Related: depID,
 			})
 
+			qual := []qualifier{{
+				key:   "mediaType",
+				value: string(desc.MediaType),
+			}, {
+				key:   "arch",
+				value: desc.Platform.Architecture,
+			}, {
+				key:   "os",
+				value: desc.Platform.OS,
+			}}
+			if desc.Platform.Variant != "" {
+				qual = append(qual, qualifier{
+					key:   "variant",
+					value: desc.Platform.Variant,
+				})
+			}
+			if desc.Platform.OSVersion != "" {
+				qual = append(qual, qualifier{
+					key:   "os-version",
+					value: desc.Platform.OSVersion,
+				})
+			}
+			for _, feat := range desc.Platform.OSFeatures {
+				qual = append(qual, qualifier{
+					key:   "os-feature",
+					value: feat,
+				})
+			}
+
 			doc.Packages = append(doc.Packages, Package{
 				ID:      depID,
 				Name:    imageDigest.String(),
@@ -280,10 +315,7 @@ func GenerateIndexSPDX(koVersion string, sii oci.SignedImageIndex) ([]byte, erro
 				ExternalRefs: []ExternalRef{{
 					Category: "PACKAGE_MANAGER",
 					Type:     "purl",
-					Locator: ociRef("image", imageDigest, qualifier{
-						key:   "arch",
-						value: desc.Platform.Architecture,
-					}),
+					Locator:  ociRef("image", imageDigest, qual...),
 				}},
 				Checksums: []Checksum{{
 					Algorithm: strings.ToUpper(imageDigest.Algorithm),
diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go
index a5c0f02e..fa63765a 100644
--- a/pkg/build/gobuild.go
+++ b/pkg/build/gobuild.go
@@ -638,7 +638,9 @@ func (g *gobuild) tarKoData(ref reference, platform *v1.Platform) (*bytes.Buffer
 }
 
 func createTemplateData() map[string]interface{} {
-	envVars := map[string]string{}
+	envVars := map[string]string{
+		"LDFLAGS": "",
+	}
 	for _, entry := range os.Environ() {
 		kv := strings.SplitN(entry, "=", 2)
 		envVars[kv[0]] = kv[1]
@@ -923,7 +925,7 @@ func (g *gobuild) Build(ctx context.Context, s string) (Result, error) {
 		if !ok {
 			return nil, fmt.Errorf("failed to interpret base as index: %v", base)
 		}
-		res, err = g.buildAll(ctx, s, baseIndex)
+		res, err = g.buildAll(ctx, s, baseRef, baseIndex)
 	case types.OCIManifestSchema1, types.DockerManifestSchema2:
 		baseImage, ok := base.(v1.Image)
 		if !ok {
@@ -939,7 +941,7 @@ func (g *gobuild) Build(ctx context.Context, s string) (Result, error) {
 	return res, nil
 }
 
-func (g *gobuild) buildAll(ctx context.Context, ref string, baseIndex v1.ImageIndex) (Result, error) {
+func (g *gobuild) buildAll(ctx context.Context, ref string, baseRef name.Reference, baseIndex v1.ImageIndex) (Result, error) {
 	im, err := baseIndex.IndexManifest()
 	if err != nil {
 		return nil, err
@@ -966,8 +968,12 @@ func (g *gobuild) buildAll(ctx context.Context, ref string, baseIndex v1.ImageIn
 		if err != nil {
 			return nil, fmt.Errorf("error getting matching image from index: %w", err)
 		}
-		// Carry forward the base index's annotations, which include base image annotations.
-		img = mutate.Annotations(img, im.Annotations).(v1.Image)
+		// Decorate the image with the ref of the index, and the matching
+		// platform's digest.
+		img = mutate.Annotations(img, map[string]string{
+			specsv1.AnnotationBaseImageDigest: matches[0].Digest.String(),
+			specsv1.AnnotationBaseImageName:   baseRef.Name(),
+		}).(v1.Image)
 		return g.buildOne(ctx, ref, img, matches[0].Platform)
 	}
 
@@ -983,6 +989,29 @@ func (g *gobuild) buildAll(ctx context.Context, ref string, baseIndex v1.ImageIn
 			if err != nil {
 				return err
 			}
+
+			// Decorate the image with the ref of the index, and the matching
+			// platform's digest.  The ref of the index encodes the critical
+			// repository information for fetching the base image's digest, but
+			// we leave `name` pointing at the index's full original ref to that
+			// folks could conceivably check for updates to the index over time.
+			// While the `digest` doesn't give us enough information to check
+			// for changes with a simple HEAD (because we need the full index
+			// manifest to get the per-architecture image), that optimization
+			// mainly matters for DockerHub where HEAD's are exempt from rate
+			// limiting.  However, in practice, the way DockerHub updates the
+			// indices for official images is to rebuild per-arch images and
+			// replace the per-arch images in the existing index, so an index
+			// with N manifest receives N updates.  If we only record the digest
+			// of the index here, then we cannot tell when the index updates are
+			// no-ops for us because we didn't record the digest of the actual
+			// image we used, and we would potentially end up doing Nx more work
+			// than we really need to do.
+			baseImage = mutate.Annotations(baseImage, map[string]string{
+				specsv1.AnnotationBaseImageDigest: desc.Digest.String(),
+				specsv1.AnnotationBaseImageName:   baseRef.Name(),
+			}).(v1.Image)
+
 			img, err := g.buildOne(ctx, ref, baseImage, desc.Platform)
 			if err != nil {
 				return err
diff --git a/pkg/commands/apply.go b/pkg/commands/apply.go
index d27770be..1874b5f6 100644
--- a/pkg/commands/apply.go
+++ b/pkg/commands/apply.go
@@ -17,35 +17,16 @@ package commands
 import (
 	"errors"
 	"fmt"
-	"log"
 	"os"
 	"os/exec"
-	"strings"
 
-	"github.com/google/ko/internal"
 	"github.com/google/ko/pkg/commands/options"
 	"github.com/spf13/cobra"
 	"golang.org/x/sync/errgroup"
 )
 
-const kubectlFlagsWarningTemplate = `NOTICE!
------------------------------------------------------------------
-Passing kubectl global flags to ko directly is deprecated.
-
-Instead of passing:
-    ko %s ... %s
-
-Pass kubectl global flags separated by "--":
-    ko %s ... -- %s
-
-For more information see:
-    https://github.com/google/ko/issues/317
------------------------------------------------------------------
-`
-
 // addApply augments our CLI surface with apply.
 func addApply(topLevel *cobra.Command) {
-	var kf internal.KubectlFlags
 	po := &options.PublishOptions{}
 	fo := &options.FilenameOptions{}
 	so := &options.SelectorOptions{}
@@ -105,16 +86,7 @@ func addApply(topLevel *cobra.Command) {
 			// Issue a "kubectl apply" command reading from stdin,
 			// to which we will pipe the resolved files, and any
 			// remaining flags passed after '--'.
-			argv := []string{"apply", "-f", "-"}
-			if kflags := kf.Values(); len(kflags) != 0 {
-				skflags := strings.Join(kflags, " ")
-				log.Printf(kubectlFlagsWarningTemplate,
-					"apply", skflags,
-					"apply", skflags)
-				argv = append(argv, kflags...)
-			}
-			argv = append(argv, args...)
-			kubectlCmd := exec.CommandContext(ctx, "kubectl", argv...)
+			kubectlCmd := exec.CommandContext(ctx, "kubectl", append([]string{"apply", "-f", "-"}, args...)...)
 
 			// Pass through our environment
 			kubectlCmd.Env = os.Environ()
@@ -160,7 +132,6 @@ func addApply(topLevel *cobra.Command) {
 	options.AddFileArg(apply, fo)
 	options.AddSelectorArg(apply, so)
 	options.AddBuildOptions(apply, bo)
-	internal.AddFlags(&kf, apply.Flags())
 
 	topLevel.AddCommand(apply)
 }
diff --git a/pkg/commands/create.go b/pkg/commands/create.go
index 4fe449ae..c8e80d00 100644
--- a/pkg/commands/create.go
+++ b/pkg/commands/create.go
@@ -17,12 +17,10 @@ package commands
 import (
 	"errors"
 	"fmt"
-	"log"
 	"os"
 	"os/exec"
 	"strings"
 
-	"github.com/google/ko/internal"
 	"github.com/google/ko/pkg/commands/options"
 	"github.com/spf13/cobra"
 	"golang.org/x/sync/errgroup"
@@ -30,7 +28,6 @@ import (
 
 // addCreate augments our CLI surface with apply.
 func addCreate(topLevel *cobra.Command) {
-	var kf internal.KubectlFlags
 	po := &options.PublishOptions{}
 	fo := &options.FilenameOptions{}
 	so := &options.SelectorOptions{}
@@ -90,16 +87,7 @@ func addCreate(topLevel *cobra.Command) {
 			// Issue a "kubectl create" command reading from stdin,
 			// to which we will pipe the resolved files, and any
 			// remaining flags passed after '--'.
-			argv := []string{"create", "-f", "-"}
-			if kflags := kf.Values(); len(kflags) != 0 {
-				skflags := strings.Join(stripPassword(kflags), " ")
-				log.Printf(kubectlFlagsWarningTemplate,
-					"create", skflags,
-					"create", skflags)
-				argv = append(argv, kflags...)
-			}
-			argv = append(argv, args...)
-			kubectlCmd := exec.CommandContext(ctx, "kubectl", argv...)
+			kubectlCmd := exec.CommandContext(ctx, "kubectl", append([]string{"create", "-f", "-"}, args...)...)
 
 			// Pass through our environment
 			kubectlCmd.Env = os.Environ()
@@ -145,7 +133,6 @@ func addCreate(topLevel *cobra.Command) {
 	options.AddFileArg(create, fo)
 	options.AddSelectorArg(create, so)
 	options.AddBuildOptions(create, bo)
-	internal.AddFlags(&kf, create.Flags())
 
 	topLevel.AddCommand(create)
 }
diff --git a/pkg/publish/recorder.go b/pkg/publish/recorder.go
index ed6e4fce..65cfc7c8 100644
--- a/pkg/publish/recorder.go
+++ b/pkg/publish/recorder.go
@@ -17,9 +17,13 @@ package publish
 import (
 	"context"
 	"io"
+	"strings"
 
 	"github.com/google/go-containerregistry/pkg/name"
+	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/ko/pkg/build"
+	"github.com/sigstore/cosign/pkg/oci"
+	"github.com/sigstore/cosign/pkg/oci/walk"
 )
 
 // recorder wraps a publisher implementation in a layer that recordes the published
@@ -47,7 +51,26 @@ func (r *recorder) Publish(ctx context.Context, br build.Result, ref string) (na
 	if err != nil {
 		return nil, err
 	}
-	if _, err := r.wc.Write([]byte(result.String() + "\n")); err != nil {
+
+	references := make([]string, 0, 20 /* just try to avoid resizing*/)
+	switch t := br.(type) {
+	case oci.SignedImageIndex:
+		if err := walk.SignedEntity(ctx, t, func(ctx context.Context, se oci.SignedEntity) error {
+			// Both of the SignedEntity types implement Digest()
+			h, err := se.(interface{ Digest() (v1.Hash, error) }).Digest()
+			if err != nil {
+				return err
+			}
+			references = append(references, result.Context().Digest(h.String()).String())
+			return nil
+		}); err != nil {
+			return nil, err
+		}
+	default:
+		references = append(references, result.String())
+	}
+
+	if _, err := r.wc.Write([]byte(strings.Join(references, "\n") + "\n")); err != nil {
 		return nil, err
 	}
 	return result, nil
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9b9484b2..02a87fc7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -366,7 +366,6 @@ github.com/spf13/cobra/doc
 # github.com/spf13/jwalterweatherman v1.1.0
 github.com/spf13/jwalterweatherman
 # github.com/spf13/pflag v1.0.5
-## explicit
 github.com/spf13/pflag
 # github.com/spf13/viper v1.12.0
 ## explicit


Test patch:
diff --git a/pkg/publish/recorder_test.go b/pkg/publish/recorder_test.go
index ac7920f4..9677d237 100644
--- a/pkg/publish/recorder_test.go
+++ b/pkg/publish/recorder_test.go
@@ -17,11 +17,13 @@ package publish
 import (
 	"bytes"
 	"context"
-	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/google/go-containerregistry/pkg/name"
+	"github.com/google/go-containerregistry/pkg/v1/random"
 	"github.com/google/ko/pkg/build"
+	"github.com/sigstore/cosign/pkg/oci/signed"
 )
 
 type cbPublish struct {
@@ -39,10 +41,13 @@ func (sp *cbPublish) Close() error {
 }
 
 func TestRecorder(t *testing.T) {
-	num := 0
+	repo := name.MustParseReference("docker.io/ubuntu:latest")
 	inner := &cbPublish{cb: func(c context.Context, b build.Result, s string) (name.Reference, error) {
-		num++
-		return name.ParseReference(fmt.Sprintf("ubuntu:%d", num))
+		h, err := b.Digest()
+		if err != nil {
+			return nil, err
+		}
+		return repo.Context().Digest(h.String()), nil
 	}}
 
 	buf := bytes.NewBuffer(nil)
@@ -52,21 +57,45 @@ func TestRecorder(t *testing.T) {
 		t.Fatalf("NewRecorder() = %v", err)
 	}
 
-	if _, err := recorder.Publish(context.Background(), nil, ""); err != nil {
+	img, err := random.Image(3, 3)
+	if err != nil {
+		t.Fatalf("random.Image() = %v", err)
+	}
+	si := signed.Image(img)
+
+	index, err := random.Index(3, 3, 2)
+	if err != nil {
+		t.Fatalf("random.Image() = %v", err)
+	}
+	sii := signed.ImageIndex(index)
+
+	if _, err := recorder.Publish(context.Background(), si, ""); err != nil {
 		t.Errorf("recorder.Publish() = %v", err)
 	}
-	if _, err := recorder.Publish(context.Background(), nil, ""); err != nil {
+	if _, err := recorder.Publish(context.Background(), si, ""); err != nil {
 		t.Errorf("recorder.Publish() = %v", err)
 	}
-	if _, err := recorder.Publish(context.Background(), nil, ""); err != nil {
+	if _, err := recorder.Publish(context.Background(), sii, ""); err != nil {
 		t.Errorf("recorder.Publish() = %v", err)
 	}
 	if err := recorder.Close(); err != nil {
 		t.Errorf("recorder.Close() = %v", err)
 	}
 
-	want, got := "ubuntu:1\nubuntu:2\nubuntu:3\n", buf.String()
-	if got != want {
-		t.Errorf("buf.String() = %s, wanted %s", got, want)
+	refs := strings.Split(strings.TrimSpace(buf.String()), "\n")
+
+	if want, got := len(refs), 5; got != want {
+		t.Errorf("len(refs) = %d, wanted %d", got, want)
+	}
+
+	for _, s := range refs {
+		ref, err := name.ParseReference(s)
+		if err != nil {
+			t.Fatalf("name.ParseReference() = %v", err)
+		}
+		// Don't compare the digests, they are random.
+		if want, got := repo.Context().String(), ref.Context().String(); want != got {
+			t.Errorf("reference repo = %v, wanted %v", got, want)
+		}
 	}
 }