Skip to content
This repository has been archived by the owner on Jan 19, 2023. It is now read-only.

Show container image manifest for pods #2810

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .github/workflows/verify-generated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
uses: actions/setup-go@v2
with:
go-version: 1.16.x
- name: Install libraries
run: sudo apt-get update && sudo apt install -y libgpgme-dev libassuan-dev libdevmapper-dev libbtrfs-dev
- name: Checkout code
uses: actions/checkout@v2
- name: Verify generated code
Expand Down
11 changes: 6 additions & 5 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
BUILD_TIME = time.Now().UTC().Format(time.RFC3339)
LD_FLAGS = fmt.Sprintf("-X \"main.buildTime=%s\" -X main.gitCommit=%s", BUILD_TIME, GIT_COMMIT)
GO_FLAGS = fmt.Sprintf("-ldflags=%s", LD_FLAGS)
IMAGE_FLAGS = "exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp"
)

func main() {
Expand Down Expand Up @@ -262,7 +263,7 @@ func goInstall() {

func generate() {
removeFakes()
runCmd("go", nil, "generate", "-v", "./pkg/...", "./internal/...")
runCmd("go", nil, "generate", "-tags", IMAGE_FLAGS, "-v", "./pkg/...", "./internal/...")
}

func build() {
Expand All @@ -273,7 +274,7 @@ func build() {
if runtime.GOOS == "windows" {
artifact = "octant.exe"
}
runCmd("go", nil, "build", "-tags", "embedded", "-mod=vendor", "-o", "build/"+artifact, GO_FLAGS, "-v", "./cmd/octant")
runCmd("go", nil, "build", "-tags", "embedded " + IMAGE_FLAGS, "-mod=vendor", "-o", "build/"+artifact, GO_FLAGS, "-v", "./cmd/octant")
}

// buildElectron builds an Octant binary without web assets
Expand All @@ -294,7 +295,7 @@ func buildElectron() {
if runtime.GOOS == "windows" {
artifact = "octant.exe"
}
runCmd("go", nil, "build", "-mod=vendor", "-o", "web/extraResources/"+artifact, GO_FLAGS, "-v", "./cmd/octant")
runCmd("go", nil, "build", "-tags", IMAGE_FLAGS, "-mod=vendor", "-o", "web/extraResources/"+artifact, GO_FLAGS, "-v", "./cmd/octant")
}

func runDev() {
Expand All @@ -307,11 +308,11 @@ func runDev() {
}

func test() {
runCmd("go", nil, "test", "-v", "./internal/...", "./pkg/...")
runCmd("go", nil, "test", "-tags", IMAGE_FLAGS, "-v", "./internal/...", "./pkg/...")
}

func vet() {
runCmd("go", nil, "vet", "./internal/...", "./pkg/...")
runCmd("go", nil, "vet", "-tags", IMAGE_FLAGS, "./internal/...", "./pkg/...")
goFmt(false)
}

Expand Down
1 change: 1 addition & 0 deletions changelogs/unreleased/156-mklanjsek
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show container image manifest for pods
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.16

require (
contrib.go.opencensus.io/exporter/jaeger v0.2.1
github.com/containers/image/v5 v5.14.0
github.com/davecgh/go-spew v1.1.1
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/dop251/goja v0.0.0-20200629185240-bfd59704b500
Expand All @@ -28,7 +29,6 @@ require (
github.com/hashicorp/golang-lru v0.5.4
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/iancoleman/strcase v0.2.0
github.com/imdario/mergo v0.3.11 // indirect
github.com/json-iterator/go v1.1.11
github.com/onsi/ginkgo v1.14.0 // indirect
github.com/pkg/errors v0.9.1
Expand Down
445 changes: 442 additions & 3 deletions go.sum

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions internal/printer/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"fmt"

"github.com/vmware-tanzu/octant/internal/api"
"github.com/vmware-tanzu/octant/internal/util/json"

"github.com/vmware-tanzu/octant/internal/log"
Expand Down Expand Up @@ -97,6 +98,8 @@ func (cc *ContainerConfiguration) Create() (*component.Summary, error) {
}

var containerStatus *corev1.ContainerStatus
var hostOS = "linux"

if pod, ok := cc.parent.(*corev1.Pod); ok {
var err error
containerStatus, err = findContainerStatus(pod, cc.container.Name, cc.isInit)
Expand All @@ -113,6 +116,9 @@ func (cc *ContainerConfiguration) Create() (*component.Summary, error) {
title = "Ephemeral Container"
}
}
if api.IsWindowsContainer(pod) {
hostOS = "windows"
}
}

sections := component.SummarySections{}
Expand All @@ -122,6 +128,14 @@ func (cc *ContainerConfiguration) Create() (*component.Summary, error) {
sections.AddText("Image ID", containerStatus.ImageID)
}

manifest, configuration, err := ManifestManager.GetImageManifest(cc.context, hostOS, c.Image)
if err == nil {
sections.Add("Image Manifest", component.NewJSONEditor(manifest, true))
sections.Add("Image Configuration", component.NewJSONEditor(configuration, true))
} else {
sections.Add("Image Manifest", component.NewText(fmt.Sprintf("Unable to load image manifest %s", err)))
}

hostPorts := describeContainerHostPorts(c.Ports)
if hostPorts != "" {
sections.AddText("Host Ports", hostPorts)
Expand Down
16 changes: 16 additions & 0 deletions internal/printer/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ func Test_ContainerConfiguration(t *testing.T) {
Header: "Image ID",
Content: component.NewText("nginx-image-id"),
},
{
Header: "Image Manifest",
Content: component.NewJSONEditor("{\"manifests\":[{\"digest\":\"sha256:e770165fef9e36b990882a4083d8ccf5e29e469a8609bb6b2e3b47d9510e2c8d\",\"mediaType\":\"application\\/vnd.docker.distribution.manifest.v2+json\",\"platform\":{\"architecture\":\"amd64\",\"os\":\"linux\"},\"size\":948},{\"digest\":\"sha256:26687467368eba1745b3af5f673156e5598b0d3609ddc041d4afb3000a7c97c4\",\"mediaType\":\"application\\/vnd.docker.distribution.manifest.v2+json\",\"platform\":{\"architecture\":\"arm\",\"os\":\"linux\",\"variant\":\"v7\"},\"size\":948},{\"digest\":\"sha256:322d209ca0e9dcd69cf1bb9354cb2c573255e96689f31b0964753389b780269c\",\"mediaType\":\"application\\/vnd.docker.distribution.manifest.v2+json\",\"platform\":{\"architecture\":\"arm64\",\"os\":\"linux\",\"variant\":\"v8\"},\"size\":948},{\"digest\":\"sha256:2393dbb3ac0f27a4b097908f78510aa20dce07c029540762447ab4731119bab7\",\"mediaType\":\"application\\/vnd.docker.distribution.manifest.v2+json\",\"platform\":{\"architecture\":\"386\",\"os\":\"linux\"},\"size\":948},{\"digest\":\"sha256:16f53d8a8fcef518bfc7ad0b87f572c036eedc5307a2539e4c73741a7fe8ea76\",\"mediaType\":\"application\\/vnd.docker.distribution.manifest.v2+json\",\"platform\":{\"architecture\":\"ppc64le\",\"os\":\"linux\"},\"size\":948},{\"digest\":\"sha256:a89d88340baf686e95076902c5f89bd54755cbb324eaae5a2a470f98db342f55\",\"mediaType\":\"application\\/vnd.docker.distribution.manifest.v2+json\",\"platform\":{\"architecture\":\"s390x\",\"os\":\"linux\"},\"size\":948}],\"mediaType\":\"application\\/vnd.docker.distribution.manifest.list.v2+json\",\"schemaVersion\":2}", true),
},
{
Header: "Image Configuration",
Content: component.NewJSONEditor("{\n \"created\": \"2019-05-08T03:01:41.947151778Z\",\n \"architecture\": \"amd64\",\n \"os\": \"linux\",\n \"config\": {\n \"ExposedPorts\": {\n \"80/tcp\": {}\n },\n \"Env\": [\n \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n \"NGINX_VERSION=1.15.12-1~stretch\",\n \"NJS_VERSION=1.15.12.0.3.1-1~stretch\"\n ],\n \"Cmd\": [\n \"nginx\",\n \"-g\",\n \"daemon off;\"\n ],\n \"Labels\": {\n \"maintainer\": \"NGINX Docker Maintainers <[email protected]>\"\n },\n \"StopSignal\": \"SIGTERM\"\n },\n \"rootfs\": {\n \"type\": \"layers\",\n \"diff_ids\": [\n \"sha256:6270adb5794c6987109e54af00ab456977c5d5cc6f1bc52c1ce58d32ec0f15f4\",\n \"sha256:6ba094226eea86e21761829b88bdfdc9feb14bd83d60fb7e666f0943253657e8\",\n \"sha256:332fa54c58864e2dcd3df0ad88c69b2707d45f2d8121dad6278a15148900e490\"\n ]\n },\n \"history\": [\n {\n \"created\": \"2019-05-08T00:33:32.152758355Z\",\n \"created_by\": \"/bin/sh -c #(nop) ADD file:fcb9328ea4c1156709f3d04c3d9a5f3667e77fb36a4a83390ae2495555fc0238 in / \"\n },\n {\n \"created\": \"2019-05-08T00:33:32.718284983Z\",\n \"created_by\": \"/bin/sh -c #(nop) CMD [\\\"bash\\\"]\",\n \"empty_layer\": true\n },\n {\n \"created\": \"2019-05-08T03:01:16.010671568Z\",\n \"created_by\": \"/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers <[email protected]>\",\n \"empty_layer\": true\n },\n {\n \"created\": \"2019-05-08T03:01:16.175452264Z\",\n \"created_by\": \"/bin/sh -c #(nop) ENV NGINX_VERSION=1.15.12-1~stretch\",\n \"empty_layer\": true\n },\n {\n \"created\": \"2019-05-08T03:01:16.36342084Z\",\n \"created_by\": \"/bin/sh -c #(nop) ENV NJS_VERSION=1.15.12.0.3.1-1~stretch\",\n \"empty_layer\": true\n },\n {\n \"created\": \"2019-05-08T03:01:40.497446007Z\",\n \"created_by\": \"/bin/sh -c set -x \\t&& apt-get update \\t&& apt-get install --no-install-recommends --no-install-suggests -y gnupg1 apt-transport-https ca-certificates \\t&& \\tNGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \\tfound=''; \\tfor server in \\t\\tha.pool.sks-keyservers.net \\t\\thkp://keyserver.ubuntu.com:80 \\t\\thkp://p80.pool.sks-keyservers.net:80 \\t\\tpgp.mit.edu \\t; do \\t\\techo \\\"Fetching GPG key $NGINX_GPGKEY from $server\\\"; \\t\\tapt-key adv --keyserver \\\"$server\\\" --keyserver-options timeout=10 --recv-keys \\\"$NGINX_GPGKEY\\\" && found=yes && break; \\tdone; \\ttest -z \\\"$found\\\" && echo >&2 \\\"error: failed to fetch GPG key $NGINX_GPGKEY\\\" && exit 1; \\tapt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \\t&& dpkgArch=\\\"$(dpkg --print-architecture)\\\" \\t&& nginxPackages=\\\" \\t\\tnginx=${NGINX_VERSION} \\t\\tnginx-module-xslt=${NGINX_VERSION} \\t\\tnginx-module-geoip=${NGINX_VERSION} \\t\\tnginx-module-image-filter=${NGINX_VERSION} \\t\\tnginx-module-njs=${NJS_VERSION} \\t\\\" \\t&& case \\\"$dpkgArch\\\" in \\t\\tamd64|i386) \\t\\t\\techo \\\"deb https://nginx.org/packages/mainline/debian/ stretch nginx\\\" >> /etc/apt/sources.list.d/nginx.list \\t\\t\\t&& apt-get update \\t\\t\\t;; \\t\\t*) \\t\\t\\techo \\\"deb-src https://nginx.org/packages/mainline/debian/ stretch nginx\\\" >> /etc/apt/sources.list.d/nginx.list \\t\\t\\t\\t\\t\\t&& tempDir=\\\"$(mktemp -d)\\\" \\t\\t\\t&& chmod 777 \\\"$tempDir\\\" \\t\\t\\t\\t\\t\\t&& savedAptMark=\\\"$(apt-mark showmanual)\\\" \\t\\t\\t\\t\\t\\t&& apt-get update \\t\\t\\t&& apt-get build-dep -y $nginxPackages \\t\\t\\t&& ( \\t\\t\\t\\tcd \\\"$tempDir\\\" \\t\\t\\t\\t&& DEB_BUILD_OPTIONS=\\\"nocheck parallel=$(nproc)\\\" \\t\\t\\t\\t\\tapt-get source --compile $nginxPackages \\t\\t\\t) \\t\\t\\t\\t\\t\\t&& apt-mark showmanual | xargs apt-mark auto > /dev/null \\t\\t\\t&& { [ -z \\\"$savedAptMark\\\" ] || apt-mark manual $savedAptMark; } \\t\\t\\t\\t\\t\\t&& ls -lAFh \\\"$tempDir\\\" \\t\\t\\t&& ( cd \\\"$tempDir\\\" && dpkg-scanpackages . > Packages ) \\t\\t\\t&& grep '^Package: ' \\\"$tempDir/Packages\\\" \\t\\t\\t&& echo \\\"deb [ trusted=yes ] file://$tempDir ./\\\" > /etc/apt/sources.list.d/temp.list \\t\\t\\t&& apt-get -o Acquire::GzipIndexes=false update \\t\\t\\t;; \\tesac \\t\\t&& apt-get install --no-install-recommends --no-install-suggests -y \\t\\t\\t\\t\\t\\t$nginxPackages \\t\\t\\t\\t\\t\\tgettext-base \\t&& apt-get remove --purge --auto-remove -y apt-transport-https ca-certificates && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \\t\\t&& if [ -n \\\"$tempDir\\\" ]; then \\t\\tapt-get purge -y --auto-remove \\t\\t&& rm -rf \\\"$tempDir\\\" /etc/apt/sources.list.d/temp.list; \\tfi\"\n },\n {\n \"created\": \"2019-05-08T03:01:41.355881721Z\",\n \"created_by\": \"/bin/sh -c ln -sf /dev/stdout /var/log/nginx/access.log \\t&& ln -sf /dev/stderr /var/log/nginx/error.log\"\n },\n {\n \"created\": \"2019-05-08T03:01:41.538214273Z\",\n \"created_by\": \"/bin/sh -c #(nop) EXPOSE 80\",\n \"empty_layer\": true\n },\n {\n \"created\": \"2019-05-08T03:01:41.740886057Z\",\n \"created_by\": \"/bin/sh -c #(nop) STOPSIGNAL SIGTERM\",\n \"empty_layer\": true\n },\n {\n \"created\": \"2019-05-08T03:01:41.947151778Z\",\n \"created_by\": \"/bin/sh -c #(nop) CMD [\\\"nginx\\\" \\\"-g\\\" \\\"daemon off;\\\"]\",\n \"empty_layer\": true\n }\n ]\n}", true),
},
{
Header: "Host Ports",
Content: component.NewText("80/TCP, 8080/TCP"),
Expand Down Expand Up @@ -262,6 +270,14 @@ func Test_ContainerConfiguration(t *testing.T) {
Header: "Image ID",
Content: component.NewText("busybox-image-id"),
},
{
Header: "Image Manifest",
Content: component.NewJSONEditor("{\n \"schemaVersion\": 2,\n \"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\",\n \"manifests\": [\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 527,\n \"digest\": \"sha256:74f634b1bc1bd74535d5209589734efbd44a25f4e2dc96d78784576a3eb5b335\",\n \"platform\": {\n \"architecture\": \"amd64\",\n \"os\": \"linux\"\n }\n },\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 527,\n \"digest\": \"sha256:35e28b647bd4976b7cacfaa32b7b253817d0881d77b6cda731ad46a29d08c2cb\",\n \"platform\": {\n \"architecture\": \"arm\",\n \"os\": \"linux\",\n \"variant\": \"v5\"\n }\n },\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 527,\n \"digest\": \"sha256:420befcb0c197618f0252108d553d8a112e291e2a6a75d8a2b4933f511480ea3\",\n \"platform\": {\n \"architecture\": \"arm\",\n \"os\": \"linux\",\n \"variant\": \"v6\"\n }\n },\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 527,\n \"digest\": \"sha256:4df1e7dbe58b7fe24145291700e4fdf89a80677ffeb9b972840b42e3ec065e1f\",\n \"platform\": {\n \"architecture\": \"arm\",\n \"os\": \"linux\",\n \"variant\": \"v7\"\n }\n },\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 527,\n \"digest\": \"sha256:859d41e4316c182cb559f9ae3c5ffcac8602ee1179794a1707c06cd092a008d3\",\n \"platform\": {\n \"architecture\": \"arm64\",\n \"os\": \"linux\",\n \"variant\": \"v8\"\n }\n },\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 527,\n \"digest\": \"sha256:19f468f7dde9dc85d1576e6eb244b190661764199e21fcb53d84378bef16e334\",\n \"platform\": {\n \"architecture\": \"386\",\n \"os\": \"linux\"\n }\n },\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 528,\n \"digest\": \"sha256:2d8967e4a68583a4bb2d7e236c60a1d72a585439b41e7a77555edad8df0f2bf4\",\n \"platform\": {\n \"architecture\": \"ppc64le\",\n \"os\": \"linux\"\n }\n },\n {\n \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n \"size\": 528,\n \"digest\": \"sha256:67510360fd7c837d71ecfbd9f7991d72a2d2cbda3b383115a0dda0f0936b57f6\",\n \"platform\": {\n \"architecture\": \"s390x\",\n \"os\": \"linux\"\n }\n }\n ]\n}", true),
},
{
Header: "Image Configuration",
Content: component.NewJSONEditor("{\n \"created\": \"2018-05-23T21:19:31.132152818Z\",\n \"architecture\": \"amd64\",\n \"os\": \"linux\",\n \"config\": {\n \"Env\": [\n \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n ],\n \"Cmd\": [\n \"sh\"\n ]\n },\n \"rootfs\": {\n \"type\": \"layers\",\n \"diff_ids\": [\n \"sha256:432b65032b9466b4dadcc5c7b11701e71d21c18400aae946b101ad16be62333a\"\n ]\n },\n \"history\": [\n {\n \"created\": \"2018-05-23T21:19:30.902651601Z\",\n \"created_by\": \"/bin/sh -c #(nop) ADD file:5f0439d8328ab58c087cd067c91ce92765da98916d91b083df6590477b7b9f19 in / \"\n },\n {\n \"created\": \"2018-05-23T21:19:31.132152818Z\",\n \"created_by\": \"/bin/sh -c #(nop) CMD [\\\"sh\\\"]\",\n \"empty_layer\": true\n }\n ]\n}", true),
},
{
Header: "Command",
Content: component.NewText("['sh']"),
Expand Down
89 changes: 89 additions & 0 deletions internal/printer/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package printer

import (
context "context"
"fmt"
"strings"
"sync"

"github.com/containers/image/v5/image"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"

"github.com/vmware-tanzu/octant/internal/util/json"
)

type ImageManifest struct {
Manifest string
Configuration string
}

type ImageEntry struct {
ImageName string
HostOS string
}

type ManifestConfiguration struct {
imageCache map[ImageEntry]ImageManifest
imageLock sync.Mutex
}

var (
ManifestManager = *NewManifestConfiguration()
)

func NewManifestConfiguration() *ManifestConfiguration {
mc := &ManifestConfiguration{}
return mc
}

func (manifest *ManifestConfiguration) GetImageManifest(ctx context.Context, hostOS, imageName string) (string, string, error) {
parts := strings.SplitN(imageName, "://", 2) // if format not specified, assume docker
if len(parts) != 2 {
imageName = "docker://" + imageName
}

imageEntry := ImageEntry{ImageName: imageName, HostOS: hostOS}
if _, ok := manifest.imageCache[imageEntry]; ok {
return manifest.imageCache[imageEntry].Manifest, manifest.imageCache[imageEntry].Configuration, nil
}

manifest.imageLock.Lock()
defer manifest.imageLock.Unlock()

srcRef, err := alltransports.ParseImageName(imageName)
if err != nil {
return "", "", fmt.Errorf("error parsing image name for image %s: %w", imageName, err)
}

systemCtx := &types.SystemContext{OSChoice: hostOS}

imageSrc, err := srcRef.NewImageSource(ctx, systemCtx)
if err != nil {
return "", "", fmt.Errorf("error creating image source for image %s: %w", imageName, err)
}

rawManifest, _, err := imageSrc.GetManifest(ctx, nil)
if err != nil {
return "", "", fmt.Errorf("error getting manifest for for image %s: %w", imageName, err)
}

image, err := image.FromUnparsedImage(ctx, systemCtx, image.UnparsedInstance(imageSrc, nil))
if err != nil {
return "", "", fmt.Errorf("error parsing manifest for for image %s: %w", imageName, err)
}

rawConfiguration, err := image.OCIConfig(ctx)
if err != nil {
return "", "", fmt.Errorf("error getting image config blob for for image %s: %w", imageName, err)
}

configOutput, err := json.MarshalIndent(rawConfiguration, "", " ")

if manifest.imageCache == nil {
manifest.imageCache = make(map[ImageEntry]ImageManifest)
}
manifest.imageCache[imageEntry] = ImageManifest{string(rawManifest), string(configOutput)}

return string(rawManifest), string(configOutput), nil
}
Loading