Skip to content

Commit

Permalink
Merge pull request #1901 from franknstyle/inspect
Browse files Browse the repository at this point in the history
add images inspect command
  • Loading branch information
franknstyle committed May 2, 2023
2 parents d65fe98 + d1b3ac5 commit 4b36969
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 13 deletions.
54 changes: 48 additions & 6 deletions cmd/sonobuoy/app/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,7 @@ var (
)

func runListImages(flags imagesFlags) {
var client image.Client
if flags.dryRun {
client = image.DryRunClient{}
} else {
client = image.NewDockerClient()
}
client := image.NewDockerClient()
version, err := getClusterVersion(flags.k8sVersion, flags.kubeconfig)
if err != nil {
errlog.LogError(err)
Expand All @@ -72,6 +67,23 @@ func runListImages(flags imagesFlags) {
}
}

func runInspectImages(flags imagesFlags) {
client := image.NewDockerClient()
version, err := getClusterVersion(flags.k8sVersion, flags.kubeconfig)
if err != nil {
errlog.LogError(err)
os.Exit(1)
}

if errs := inspectImages(flags.plugins, flags.pluginEnvs, version, client); err != nil {
for _, err := range errs {
errlog.LogError(err)
}
os.Exit(1)
}
logrus.Info("configured images available")
}

func NewCmdImages() *cobra.Command {
var flags imagesFlags
// Main command
Expand All @@ -95,10 +107,31 @@ func NewCmdImages() *cobra.Command {
cmd.AddCommand(pushCmd())
cmd.AddCommand(downloadCmd())
cmd.AddCommand(deleteCmd())
cmd.AddCommand(inspectCmd())

return cmd
}

func inspectCmd() *cobra.Command {
var flags imagesFlags

inspectCmd := &cobra.Command{
Use: "inspect",
Short: "Inspect images",
Long: "Inspect if image is available in the registry",
Run: func(cmd *cobra.Command, args []string) {
runInspectImages(flags)
},
Args: cobra.ExactArgs(0),
}
AddKubeconfigFlag(&flags.kubeconfig, inspectCmd.Flags())
AddPluginListFlag(&flags.plugins, inspectCmd.Flags())
AddDryRunFlag(&flags.dryRun, inspectCmd.Flags())
AddKubernetesVersionFlag(&flags.k8sVersion, &transformSink, inspectCmd.Flags())

return inspectCmd
}

func listCmd() *cobra.Command {
var flags imagesFlags

Expand Down Expand Up @@ -296,6 +329,15 @@ func listImages(plugins []string, pluginEnvs PluginEnvVars, k8sVersion string, c
return nil
}

func inspectImages(plugins []string, pluginEnvs PluginEnvVars, k8sVersion string, client image.Client) []error {
images, err := collectPluginsImages(plugins, pluginEnvs, k8sVersion, client)
if err != nil {
return []error{err, errors.Errorf("unable to collect images of plugins")}
}
sort.Strings(images)
return client.InspectImages(images)
}

func pullImages(plugins []string, pluginEnvs PluginEnvVars, e2eRegistry, e2eRegistryConfig, k8sVersion string, client image.Client) []error {
images, err := collectPluginsImages(plugins, pluginEnvs, k8sVersion, client)
if err != nil {
Expand Down
67 changes: 67 additions & 0 deletions pkg/image/docker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ limitations under the License.
package docker

import (
"bytes"
"encoding/json"
"time"

"fmt"

"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/vmware-tanzu/sonobuoy/pkg/image/exec"
)

type Docker interface {
Inspect(image string, retries int) error
PullIfNotPresent(image string, retries int) error
Pull(image string, retries int) error
Push(image string, retries int) error
Expand All @@ -36,6 +42,30 @@ type Docker interface {
type LocalDocker struct {
}

type inspectResponse struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Config struct {
MediaType string `json:"mediaType"`
Size int `json:"size"`
Digest string `json:"digest"`
} `json:"config"`
Layers []struct {
MediaType string `json:"mediaType"`
Size int `json:"size"`
Digest string `json:"digest"`
} `json:"layers"`
Manifests []struct {
MediaType string `json:"mediaType"`
Size int `json:"size"`
Digest string `json:"digest"`
Platform struct {
Architecture string `json:"architecture"`
Os string `json:"os"`
} `json:"platform,omitempty"`
} `json:"manifests"`
}

func (l LocalDocker) Run(image string, entryPoint string, env map[string]string, args ...string) ([]string, error) {
dockerArgs := []string{"run", "--rm"}
if len(entryPoint) > 0 {
Expand All @@ -52,6 +82,43 @@ func (l LocalDocker) Run(image string, entryPoint string, env map[string]string,
return exec.CombinedOutputLines(cmd)
}

// Inspect
func (l LocalDocker) Inspect(image string, retries int) error {
i := inspectResponse{}
cmd := exec.Command("docker", "buildx", "imagetools", "inspect", "--raw", image)
var buff bytes.Buffer
cmd.SetStdout(&buff)
cmd.SetStderr(&buff)

err := cmd.Run()
if err != nil {
return errors.Wrap(err, "failed to run Docker command")
}
if err := json.Unmarshal(buff.Bytes(), &i); err != nil {
for i := 1; i <= retries; i++ {
log.Debug(buff.String())
log.Debugf("Image inspection: %s retrying attempt: %v", image, i)
buff.Reset()
err := cmd.Run()
if err != nil {
log.Debug(err)
time.Sleep(1 * time.Second)
}
}
return errors.Wrapf(err, "Image: %s not found in registry", image)
}

if i.Config.Digest != "" {
log.Debugf("Image: %s found in registry @%s", image, i.Config.Digest)
}

if len(i.Manifests) > 0 {
log.Debugf("Image: %s found in registry @%s", image, i.Manifests[0].Digest)
}
defer buff.Reset()
return nil
}

// PullIfNotPresent will pull an image if it is not present locally
// retrying up to "retries" times. Returns errors from pulling.
func (l LocalDocker) PullIfNotPresent(image string, retries int) error {
Expand Down
32 changes: 31 additions & 1 deletion pkg/image/docker_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package image

import (
"fmt"
"log"
"strings"
"sync"

"github.com/pkg/errors"
"github.com/vmware-tanzu/sonobuoy/pkg/image/docker"
Expand Down Expand Up @@ -85,7 +88,14 @@ func (i DockerClient) PushImages(images []TagPair, retries int) []error {
// resulting file name.
func (i DockerClient) DownloadImages(images []string, version string) (string, error) {
fileName := getTarFileName(version)

for k, image := range images {
if strings.HasPrefix(image, "invalid") {
images[k] = images[len(images)-1]
images[len(images)-1] = ""
images = images[:len(images)-1]
}
}
log.Println(images)
err := i.dockerClient.Save(images, fileName)
if err != nil {
return "", errors.Wrap(err, "couldn't save images to tar")
Expand All @@ -94,6 +104,26 @@ func (i DockerClient) DownloadImages(images []string, version string) (string, e
return fileName, nil
}

// InspectImages.
func (i DockerClient) InspectImages(images []string) []error {
errs := []error{}
var wg sync.WaitGroup
wg.Add(len(images))
for _, image := range images {
go func(image string) {
err := i.dockerClient.Inspect(image, 3)
if err != nil {
errs = append(errs, errors.Wrapf(err, "couldn't delete image: %v", image))
}
wg.Done()
}(image)
}
wg.Wait()
return errs
}

// mediaType application/vnd.docker.distribution.manifest.list.v2+json application/vnd.docker.distribution.manifest.v2+json

// DeleteImages deletes the given list of images from the local machine.
// It will retry for the provided number of retries on failure.
func (i DockerClient) DeleteImages(images []string, retries int) []error {
Expand Down
20 changes: 14 additions & 6 deletions pkg/image/docker_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ import (
var imgs = []string{"test1/foo.io/sonobuoy:x.y"}

type FakeDockerClient struct {
imageExists bool
pushFails bool
pullFails bool
tagFails bool
saveFails bool
deleteFails bool
imageExists bool
pushFails bool
pullFails bool
tagFails bool
saveFails bool
deleteFails bool
inspectFails bool
}

func (l FakeDockerClient) Run(image string, entryPoint string, env map[string]string, args ...string) ([]string, error) {
Expand All @@ -52,6 +53,13 @@ func (l FakeDockerClient) Pull(image string, retries int) error {
return nil
}

func (l FakeDockerClient) Inspect(image string, retries int) error {
if l.inspectFails {
return errors.New("inspect failed")
}
return nil
}

func (l FakeDockerClient) Push(image string, retries int) error {
if l.pushFails {
return errors.New("push failed")
Expand Down
4 changes: 4 additions & 0 deletions pkg/image/dryrun_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func (i DryRunClient) RunImage(image string, entryPoint string, env map[string]s
return []string{}, nil
}

func (i DryRunClient) InspectImages([]string) []error {
return []error{}
}

// PullImages logs the images that would be pulled.
func (i DryRunClient) PullImages(images []string, retries int) []error {
for _, image := range images {
Expand Down
1 change: 1 addition & 0 deletions pkg/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ type Client interface {
DownloadImages(images []string, version string) (string, error)
DeleteImages(images []string, retries int) []error
RunImage(image string, entrypoint string, env map[string]string, args ...string) ([]string, error)
InspectImages(images []string) []error
}

0 comments on commit 4b36969

Please sign in to comment.