Repository: google/ko
Pull request #776 (2022-07-21 13:53:19)

Set layer media types consistently
We currently always append layers with Docker media types, even if the base image is an OCI image (e.g., our new default base image, `ghcr.io/distroless/static`)

This works fine against some registries (GCR), but not against others (apparently Quay, Gitlab Registry, probably others).

This was reported in https://github.com/tektoncd/pipeline/issues/5188 since Tekton switched its base image for git-init to ghcr.io/distroless/git

This changes our layer creation to check for the type of the base image, and set layer media types accordingly.


Patch:
diff --git a/.github/workflows/go-1.18.yaml b/.github/workflows/go-1.18.yaml
index a3d684cc..cb58a6ba 100644
--- a/.github/workflows/go-1.18.yaml
+++ b/.github/workflows/go-1.18.yaml
@@ -30,6 +30,6 @@ jobs:
         go-version: ${{ matrix.user-go-version }}
         check-latest: true
     - uses: chainguard-dev/actions/setup-registry@main
-    - with:
+    - env:
         KO_DOCKER_REPO: localhost:1338
       run: ko build ./test/
diff --git a/doc/ko.md b/doc/ko.md
index 2907634f..a0d687c7 100644
--- a/doc/ko.md
+++ b/doc/ko.md
@@ -19,7 +19,6 @@ ko [flags]
 * [ko build](ko_build.md)	 - Build and publish container images from the given importpaths.
 * [ko create](ko_create.md)	 - Create the input files with image references resolved to built/pushed image digests.
 * [ko delete](ko_delete.md)	 - See "kubectl help delete" for detailed usage.
-* [ko deps](ko_deps.md)	 - Print Go module dependency information about the ko-built binary in the image
 * [ko login](ko_login.md)	 - Log in to a registry
 * [ko resolve](ko_resolve.md)	 - Print the input files with image references resolved to built/pushed image digests.
 * [ko run](ko_run.md)	 - A variant of `kubectl run` that containerizes IMPORTPATH first.
diff --git a/doc/ko_deps.md b/doc/ko_deps.md
deleted file mode 100644
index 3eb5ae18..00000000
--- a/doc/ko_deps.md
+++ /dev/null
@@ -1,39 +0,0 @@
-## ko deps
-
-Print Go module dependency information about the ko-built binary in the image
-
-### Synopsis
-
-This sub-command finds and extracts the executable binary in the image, assuming it was built by ko, and prints information about the Go module dependencies of that executable, as reported by "go version -m".
-
-If the image was not built using ko, or if it was built without embedding dependency information, this command will fail.
-
-```
-ko deps IMAGE [flags]
-```
-
-### Examples
-
-```
-
-  # Fetch and extract Go dependency information from an image:
-  ko deps docker.io/my-user/my-image:v3
-```
-
-### Options
-
-```
-  -h, --help          help for deps
-      --sbom string   Format for SBOM output (supports: spdx, cyclonedx, go.version-m). (default "spdx")
-```
-
-### Options inherited from parent commands
-
-```
-  -v, --verbose   Enable debug logs
-```
-
-### SEE ALSO
-
-* [ko](ko.md)	 - Rapidly iterate with Go, Containers, and Kubernetes.
-
diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go
index c4063439..c140052b 100644
--- a/pkg/build/gobuild.go
+++ b/pkg/build/gobuild.go
@@ -731,6 +731,20 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
 
 	ref := newRef(refStr)
 
+	// Layers should be typed to match the underlying image, since some
+	// registries reject mixed-type layers.
+	var layerMediaType types.MediaType
+	mt, err := base.MediaType()
+	if err != nil {
+		return nil, err
+	}
+	switch mt {
+	case types.OCIManifestSchema1:
+		layerMediaType = types.OCILayer
+	case types.DockerManifestSchema2:
+		layerMediaType = types.DockerLayer
+	}
+
 	cf, err := base.ConfigFile()
 	if err != nil {
 		return nil, err
@@ -762,7 +776,7 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
 	dataLayerBytes := dataLayerBuf.Bytes()
 	dataLayer, err := tarball.LayerFromOpener(func() (io.ReadCloser, error) {
 		return ioutil.NopCloser(bytes.NewBuffer(dataLayerBytes)), nil
-	}, tarball.WithCompressedCaching)
+	}, tarball.WithCompressedCaching, tarball.WithMediaType(layerMediaType))
 	if err != nil {
 		return nil, err
 	}
@@ -780,7 +794,7 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
 	appPath := path.Join(appDir, appFilename(ref.Path()))
 
 	miss := func() (v1.Layer, error) {
-		return buildLayer(appPath, file, platform)
+		return buildLayer(appPath, file, platform, layerMediaType)
 	}
 
 	binaryLayer, err := g.cache.get(ctx, file, miss)
@@ -789,7 +803,8 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
 	}
 
 	layers = append(layers, mutate.Addendum{
-		Layer: binaryLayer,
+		Layer:     binaryLayer,
+		MediaType: layerMediaType,
 		History: v1.History{
 			Author:    "ko",
 			Created:   g.creationTime,
@@ -860,7 +875,7 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
 	return si, nil
 }
 
-func buildLayer(appPath, file string, platform *v1.Platform) (v1.Layer, error) {
+func buildLayer(appPath, file string, platform *v1.Platform, layerMediaType types.MediaType) (v1.Layer, error) {
 	// Construct a tarball with the binary and produce a layer.
 	binaryLayerBuf, err := tarBinary(appPath, file, platform)
 	if err != nil {
@@ -872,7 +887,7 @@ func buildLayer(appPath, file string, platform *v1.Platform) (v1.Layer, error) {
 	}, tarball.WithCompressedCaching, tarball.WithEstargzOptions(estargz.WithPrioritizedFiles([]string{
 		// When using estargz, prioritize downloading the binary entrypoint.
 		appPath,
-	})))
+	})), tarball.WithMediaType(layerMediaType))
 }
 
 // Append appPath to the PATH environment variable, if it exists. Otherwise,
diff --git a/pkg/commands/deps.go b/pkg/commands/deps.go
index 5f6ecccc..c9e85f84 100644
--- a/pkg/commands/deps.go
+++ b/pkg/commands/deps.go
@@ -46,7 +46,8 @@ If the image was not built using ko, or if it was built without embedding depend
 		Example: `
   # Fetch and extract Go dependency information from an image:
   ko deps docker.io/my-user/my-image:v3`,
-		Args: cobra.ExactArgs(1),
+		Args:       cobra.ExactArgs(1),
+		Deprecated: "SBOMs are generated and uploaded by default; this command will be removed in a future release.",
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 


Test patch:
diff --git a/pkg/build/gobuild_test.go b/pkg/build/gobuild_test.go
index afe8e22b..ccba6845 100644
--- a/pkg/build/gobuild_test.go
+++ b/pkg/build/gobuild_test.go
@@ -1152,3 +1152,76 @@ func TestMatchesPlatformSpec(t *testing.T) {
 		}
 	}
 }
+
+func TestGoBuildConsistentMediaTypes(t *testing.T) {
+	for _, c := range []struct {
+		desc                      string
+		mediaType, layerMediaType types.MediaType
+	}{{
+		desc:           "docker types",
+		mediaType:      types.DockerManifestSchema2,
+		layerMediaType: types.DockerLayer,
+	}, {
+		desc:           "oci types",
+		mediaType:      types.OCIManifestSchema1,
+		layerMediaType: types.OCILayer,
+	}} {
+		t.Run(c.desc, func(t *testing.T) {
+			base := mutate.MediaType(empty.Image, c.mediaType)
+			l, err := random.Layer(10, c.layerMediaType)
+			if err != nil {
+				t.Fatal(err)
+			}
+			base, err = mutate.AppendLayers(base, l)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			ng, err := NewGo(
+				context.Background(),
+				"",
+				WithBaseImages(func(context.Context, string) (name.Reference, Result, error) { return baseRef, base, nil }),
+				withBuilder(writeTempFile),
+				withSBOMber(fauxSBOM),
+			)
+			if err != nil {
+				t.Fatalf("NewGo() = %v", err)
+			}
+
+			importpath := "github.com/google/ko"
+
+			result, err := ng.Build(context.Background(), StrictScheme+importpath)
+			if err != nil {
+				t.Fatalf("Build() = %v", err)
+			}
+
+			img, ok := result.(v1.Image)
+			if !ok {
+				t.Fatalf("Build() not an Image: %T", result)
+			}
+
+			ls, err := img.Layers()
+			if err != nil {
+				t.Fatalf("Layers() = %v", err)
+			}
+
+			for i, l := range ls {
+				gotMT, err := l.MediaType()
+				if err != nil {
+					t.Fatal(err)
+				}
+				if gotMT != c.layerMediaType {
+					t.Errorf("layer %d: got mediaType %q, want %q", i, gotMT, c.layerMediaType)
+				}
+			}
+
+			gotMT, err := img.MediaType()
+			if err != nil {
+				t.Fatal(err)
+			}
+			if gotMT != c.mediaType {
+				t.Errorf("got image mediaType %q, want %q", gotMT, c.layerMediaType)
+			}
+		})
+	}
+}