Merge pull request #235 from wagoodman/add-tar-support

Adding docker-archive source option
This commit is contained in:
Alex Goodman 2019-10-09 08:47:12 -04:00 committed by GitHub
commit 0a63b1714e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 113 additions and 71 deletions

View file

@ -61,14 +61,17 @@ command.
**CI Integration**
Analyze and image and get a pass/fail result based on the image efficiency and wasted space. Simply set `CI=true` in the environment when invoking any valid dive command.
**Supported Container Engines**
- Docker (default)
- Podman (linux only)
**With Multiple Image Sources and Container Engines Supported**
With the `--source` option, you can select where to fetch the container image from:
```bash
dive <your-image-tag> --engine podman
dive <your-image-tag> --source podman
```
With valid `source` options as such:
- `docker`: Docker engine (the default option)
- `docker-archive`: A Docker Tar Archive from disk
- `podman`: Podman engine (linux only)
## Installation
**Ubuntu/Debian**

View file

@ -39,7 +39,7 @@ func doAnalyzeCmd(cmd *cobra.Command, args []string) {
os.Exit(1)
}
engine, err := cmd.PersistentFlags().GetString("engine")
engine, err := cmd.PersistentFlags().GetString("source")
if err != nil {
fmt.Printf("unable to determine engine: %v\n", err)
os.Exit(1)
@ -47,8 +47,8 @@ func doAnalyzeCmd(cmd *cobra.Command, args []string) {
runtime.Run(runtime.Options{
Ci: isCi,
Engine: dive.GetEngine(engine),
ImageId: userImage,
Source: dive.ParseImageSource(engine),
Image: userImage,
ExportFile: exportFile,
CiConfig: ciConfig,
})

View file

@ -29,7 +29,7 @@ func doBuildCmd(cmd *cobra.Command, args []string) {
runtime.Run(runtime.Options{
Ci: isCi,
Engine: dive.GetEngine(engine),
Source: dive.ParseImageSource(engine),
BuildArgs: args,
ExportFile: exportFile,
CiConfig: ciConfig,

View file

@ -46,6 +46,7 @@ func init() {
func initCli() {
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dive.yaml, ~/.config/dive/*.yaml, or $XDG_CONFIG_HOME/dive.yaml)")
rootCmd.PersistentFlags().String("source", "docker", "The container engine to fetch the image from. Allowed values: "+strings.Join(dive.ImageSources, ", "))
rootCmd.PersistentFlags().BoolP("version", "v", false, "display version number")
rootCmd.Flags().BoolVar(&isCi, "ci", false, "Skip the interactive TUI and validate against CI rules (same as env var CI=true)")
rootCmd.Flags().StringVarP(&exportFile, "json", "j", "", "Skip the interactive TUI and write the layer analysis statistics to a given file.")
@ -61,11 +62,6 @@ func initCli() {
}
}
rootCmd.PersistentFlags().String("engine", "docker", "The container engine to fetch the image from. Allowed values: "+strings.Join(dive.AllowedEngines, ", "))
if err := viper.BindPFlag("container-engine", rootCmd.PersistentFlags().Lookup("engine")); err != nil {
log.Fatal("Unable to bind 'engine' flag:", err)
}
}
// initConfig reads in config file and ENV variables if set.

View file

@ -1,44 +0,0 @@
package dive
import (
"fmt"
"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/dive/image/docker"
"github.com/wagoodman/dive/dive/image/podman"
)
type Engine int
const (
EngineUnknown Engine = iota
EngineDocker
EnginePodman
)
func (engine Engine) String() string {
return [...]string{"unknown", "docker", "podman"}[engine]
}
var AllowedEngines = []string{EngineDocker.String(), EnginePodman.String()}
func GetEngine(engine string) Engine {
switch engine {
case "docker":
return EngineDocker
case "podman":
return EnginePodman
default:
return EngineUnknown
}
}
func GetImageHandler(engine Engine) (image.Resolver, error) {
switch engine {
case EngineDocker:
return docker.NewResolver(), nil
case EnginePodman:
return podman.NewResolver(), nil
}
return nil, fmt.Errorf("unable to determine image provider")
}

View file

@ -0,0 +1,51 @@
package dive
import (
"fmt"
"github.com/wagoodman/dive/dive/image"
"github.com/wagoodman/dive/dive/image/docker"
"github.com/wagoodman/dive/dive/image/podman"
)
const (
SourceUnknown ImageSource = iota
SourceDockerEngine
SourcePodmanEngine
SourceDockerArchive
)
type ImageSource int
var ImageSources = []string{SourceDockerEngine.String(), SourcePodmanEngine.String(), SourceDockerArchive.String()}
func (r ImageSource) String() string {
return [...]string{"unknown", "docker", "podman", "docker-archive"}[r]
}
func ParseImageSource(r string) ImageSource {
switch r {
case "docker":
return SourceDockerEngine
case "podman":
return SourcePodmanEngine
case "docker-archive":
return SourceDockerArchive
case "docker-tar":
return SourceDockerArchive
default:
return SourceUnknown
}
}
func GetImageResolver(r ImageSource) (image.Resolver, error) {
switch r {
case SourceDockerEngine:
return docker.NewResolverFromEngine(), nil
case SourcePodmanEngine:
return podman.NewResolverFromEngine(), nil
case SourceDockerArchive:
return docker.NewResolverFromArchive(), nil
}
return nil, fmt.Errorf("unable to determine image resolver")
}

View file

@ -0,0 +1,31 @@
package docker
import (
"fmt"
"github.com/wagoodman/dive/dive/image"
"os"
)
type archiveResolver struct{}
func NewResolverFromArchive() *archiveResolver {
return &archiveResolver{}
}
func (r *archiveResolver) Fetch(path string) (*image.Image, error) {
reader, err := os.Open(path)
if err != nil {
return nil, err
}
defer reader.Close()
img, err := NewImageArchive(reader)
if err != nil {
return nil, err
}
return img.ToImage()
}
func (r *archiveResolver) Build(args []string) (*image.Image, error) {
return nil, fmt.Errorf("build option not supported for docker archive resolver")
}

View file

@ -13,13 +13,13 @@ import (
"golang.org/x/net/context"
)
type resolver struct{}
type engineResolver struct{}
func NewResolver() *resolver {
return &resolver{}
func NewResolverFromEngine() *engineResolver {
return &engineResolver{}
}
func (r *resolver) Fetch(id string) (*image.Image, error) {
func (r *engineResolver) Fetch(id string) (*image.Image, error) {
reader, err := r.fetchArchive(id)
if err != nil {
@ -34,7 +34,7 @@ func (r *resolver) Fetch(id string) (*image.Image, error) {
return img.ToImage()
}
func (r *resolver) Build(args []string) (*image.Image, error) {
func (r *engineResolver) Build(args []string) (*image.Image, error) {
id, err := buildImageFromCli(args)
if err != nil {
return nil, err
@ -42,11 +42,11 @@ func (r *resolver) Build(args []string) (*image.Image, error) {
return r.Fetch(id)
}
func (r *resolver) fetchArchive(id string) (io.ReadCloser, error) {
func (r *engineResolver) fetchArchive(id string) (io.ReadCloser, error) {
var err error
var dockerClient *client.Client
// pull the resolver if it does not exist
// pull the engineResolver if it does not exist
ctx := context.Background()
host := os.Getenv("DOCKER_HOST")

View file

@ -149,7 +149,7 @@ func (img *ImageArchive) ToImage() (*image.Image, error) {
// build the layers array
layers := make([]*image.Layer, 0)
// note that the resolver config stores images in reverse chronological order, so iterate backwards through layers
// note that the engineResolver config stores images in reverse chronological order, so iterate backwards through layers
// as you iterate chronologically through history (ignoring history items that have no layer contents)
// Note: history is not required metadata in a docker image!
histIdx := 0

View file

@ -13,7 +13,7 @@ import (
type resolver struct{}
func NewResolver() *resolver {
func NewResolverFromEngine() *resolver {
return &resolver{}
}

View file

@ -7,8 +7,8 @@ import (
type Options struct {
Ci bool
ImageId string
Engine dive.Engine
Image string
Source dive.ImageSource
ExportFile string
CiConfig *viper.Viper
BuildArgs []string

View file

@ -25,6 +25,8 @@ func runCi(analysis *image.AnalysisResult, options Options) {
evaluator := ci.NewCiEvaluator(options.CiConfig)
pass := evaluator.Evaluate(analysis)
// todo: report should return a string?
evaluator.Report()
if pass {
@ -33,6 +35,9 @@ func runCi(analysis *image.AnalysisResult, options Options) {
os.Exit(1)
}
// todo: give channel of strings which the caller uses for fmt.print? or a more complex type?
// todo: return err? or treat like a go routine?
// todo: should there be a run() so that Run() can do the above and run() be the go routine? Then we test the behavior of run() (not Run())
func Run(options Options) {
var err error
doExport := options.ExportFile != ""
@ -43,7 +48,7 @@ func Run(options Options) {
// if build is given, get the handler based off of either the explicit runtime
imageResolver, err := dive.GetImageHandler(options.Engine)
imageResolver, err := dive.GetImageResolver(options.Source)
if err != nil {
fmt.Printf("cannot determine image provider: %v\n", err)
os.Exit(1)
@ -60,7 +65,7 @@ func Run(options Options) {
}
} else {
fmt.Println(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)")
img, err = imageResolver.Fetch(options.ImageId)
img, err = imageResolver.Fetch(options.Image)
if err != nil {
fmt.Printf("cannot fetch image: %v\n", err)
os.Exit(1)