diff --git a/pkg/notes/document/document.go b/pkg/notes/document/document.go index 8c8458a96e4..6970f7d4919 100644 --- a/pkg/notes/document/document.go +++ b/pkg/notes/document/document.go @@ -121,16 +121,42 @@ func fetchImageMetadata(dir, tag string) (*ImageMetadata, error) { } res := ImageMetadata{} - for manifest, architectures := range manifests { + + // Link the images to their corresponding Google Cloud container registry + // location. + const linkBase = "https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/" + + for manifest, tempArchitectures := range manifests { + imageName := strings.TrimPrefix(manifest, image.ProdRegistry+"/") + + architectures := []string{} + for _, architecture := range tempArchitectures { + architectures = append( + architectures, + markdownLink(architecture, linkBase+imageName+"-"+architecture), + ) + } + res = append(res, Image{ - Name: fmt.Sprintf("%s:%s", manifest, tag), + Name: markdownLink( + fmt.Sprintf("%s:%s", manifest, tag), + linkBase+imageName, + ), Architectures: architectures, }) } + sort.SliceStable(res, func(i, j int) bool { + return res[i].Name < res[j].Name + }) + return &res, nil } +func markdownLink(text, link string) string { + return fmt.Sprintf("[%s](%s)", text, link) +} + // fileInfo fetches file metadata for files in `dir` matching `patterns` func fileInfo(dir string, patterns []string, urlPrefix, tag string) ([]File, error) { var files []File @@ -430,7 +456,8 @@ func CreateDownloadsTable(w io.Writer, bucket, tars, images, prevTag, newTag str } fmt.Fprintf(w, "# %s\n\n", newTag) - fmt.Fprintf(w, "[Documentation](https://docs.k8s.io)\n\n") + fmt.Fprint(w, markdownLink("Documentation", "https://docs.k8s.io")) + fmt.Fprintf(w, "\n\n") fmt.Fprintf(w, "## Downloads for %s\n\n", newTag) @@ -454,7 +481,8 @@ func CreateDownloadsTable(w io.Writer, bucket, tars, images, prevTag, newTag str fmt.Fprintln(w, "-------- | -----------") for _, f := range files[header] { - fmt.Fprintf(w, "[%s](%s) | `%s`\n", f.Name, f.URL, f.Checksum) + fmt.Fprint(w, markdownLink(f.Name, f.URL)) + fmt.Fprintf(w, " | `%s`\n", f.Checksum) } fmt.Fprintln(w, "") } diff --git a/pkg/notes/document/document_test.go b/pkg/notes/document/document_test.go index 3e9cff60fa9..a7357454f6e 100644 --- a/pkg/notes/document/document_test.go +++ b/pkg/notes/document/document_test.go @@ -18,6 +18,7 @@ package document import ( "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -27,6 +28,7 @@ import ( "k8s.io/release/pkg/notes" "k8s.io/release/pkg/notes/options" "k8s.io/release/pkg/release" + "sigs.k8s.io/release-utils/command" ) func TestFileMetadata(t *testing.T) { @@ -256,6 +258,37 @@ func setupTestDir(t *testing.T, dir string) { filepath.Join(dir, file), []byte{1, 2, 3}, os.FileMode(0o644), )) } + for _, arch := range []string{"amd64", "arm", "arm64", "ppc64le", "s390x"} { + archDir := filepath.Join(dir, release.ImagesPath, arch) + require.Nil(t, os.MkdirAll(archDir, fs.FileMode(0o755))) + + for _, file := range []string{ + fmt.Sprintf("conformance-%s.tar", arch), + "kube-apiserver.tar", + "kube-controller-manager.tar", + "kube-proxy.tar", + "kube-scheduler.tar", + } { + repoTagTarball(t, + filepath.Join(archDir, file), + "k8s.gcr.io/"+strings.TrimSuffix(file, ".tar")+":v1.16.0", + ) + } + } +} + +func repoTagTarball(t *testing.T, path, repoTag string) { + const manifestJSON = "manifest.json" + manifestJSONPath := filepath.Join(filepath.Dir(path), manifestJSON) + require.Nil(t, os.WriteFile( + manifestJSONPath, + []byte(fmt.Sprintf(`[{"RepoTags": ["%s"]}]`, repoTag)), + os.FileMode(0o644), + )) + require.Nil(t, command.NewWithWorkDir(filepath.Dir(path), + "tar", "cf", path, manifestJSON, + ).RunSilentSuccess()) + require.Nil(t, os.RemoveAll(manifestJSONPath)) } func TestNew(t *testing.T) { @@ -514,7 +547,7 @@ func TestDocument_RenderMarkdownTemplate(t *testing.T) { } // When - got, err := doc.RenderMarkdownTemplate(release.ProductionBucket, dir, "", templateSpec) + got, err := doc.RenderMarkdownTemplate(release.ProductionBucket, dir, dir, templateSpec) // Then require.NoError(t, err, "Unexpected error.") diff --git a/pkg/notes/document/template.go b/pkg/notes/document/template.go index e03d2bc4c5c..a7e98d54318 100644 --- a/pkg/notes/document/template.go +++ b/pkg/notes/document/template.go @@ -30,9 +30,10 @@ const defaultReleaseNotesTemplate = ` {{- $CurrentRevision := .CurrentRevision -}} {{- $PreviousRevision := .PreviousRevision -}} -{{if .FileDownloads}} +{{if or .FileDownloads .ImageDownloads}} ## Downloads for {{$CurrentRevision}} +{{- if .FileDownloads -}} {{- with .FileDownloads.Source }} ### Source Code @@ -65,8 +66,18 @@ filename | sha512 hash -------- | ----------- {{range .}}[{{.Name}}]({{.URL}}) | {{.Checksum}}{{println}}{{end}} {{end -}} -{{- end -}} +{{if .ImageDownloads}} +{{- with .ImageDownloads -}} +` + ContainerImagesDescription + ` +name | architectures +---- | ------------- +{{range .}}{{.Name}} | {{ range $i, $a := .Architectures}}{{if $i}}, {{end}}{{$a}}{{end}}{{println}}{{end}} +{{end -}} + +{{end -}} +{{end -}} +{{- end -}} {{with .CVEList -}} ## Important Security Information diff --git a/pkg/notes/document/testdata/document.md.golden b/pkg/notes/document/testdata/document.md.golden index f797c664ba6..b7eab8dc81d 100644 --- a/pkg/notes/document/testdata/document.md.golden +++ b/pkg/notes/document/testdata/document.md.golden @@ -43,6 +43,20 @@ filename | sha512 hash [kubernetes-node-linux-s390x.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-linux-s390x.tar.gz) | 27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29 [kubernetes-node-windows-amd64.tar.gz](https://dl.k8s.io/v1.16.1/kubernetes-node-windows-amd64.tar.gz) | 27864cc5219a951a7a6e52b8c8dddf6981d098da1658d96258c870b2c88dfbcb51841aea172a28bafa6a79731165584677066045c959ed0f9929688d04defc29 +### Container Images + +All container images are available as manifest lists and support the described +architectures. It is also possible to pull a specific architecture directly by +adding the "-$ARCH" suffix to the container image name. + +name | architectures +---- | ------------- +[k8s.gcr.io/conformance:v1.16.1](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/conformance) | [amd64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/conformance-amd64), [arm](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/conformance-arm), [arm64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/conformance-arm64), [ppc64le](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/conformance-ppc64le), [s390x](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/conformance-s390x) +[k8s.gcr.io/kube-apiserver:v1.16.1](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-apiserver) | [amd64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-apiserver-amd64), [arm](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-apiserver-arm), [arm64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-apiserver-arm64), [ppc64le](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-apiserver-ppc64le), [s390x](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-apiserver-s390x) +[k8s.gcr.io/kube-controller-manager:v1.16.1](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-controller-manager) | [amd64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-controller-manager-amd64), [arm](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-controller-manager-arm), [arm64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-controller-manager-arm64), [ppc64le](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-controller-manager-ppc64le), [s390x](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-controller-manager-s390x) +[k8s.gcr.io/kube-proxy:v1.16.1](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-proxy) | [amd64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-proxy-amd64), [arm](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-proxy-arm), [arm64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-proxy-arm64), [ppc64le](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-proxy-ppc64le), [s390x](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-proxy-s390x) +[k8s.gcr.io/kube-scheduler:v1.16.1](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-scheduler) | [amd64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-scheduler-amd64), [arm](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-scheduler-arm), [arm64](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-scheduler-arm64), [ppc64le](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-scheduler-ppc64le), [s390x](https://console.cloud.google.com/gcr/images/k8s-artifacts-prod/us/kube-scheduler-s390x) + ## Urgent Upgrade Notes ### (No, really, you MUST read this before you upgrade)