summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Goodman <wagoodman@gmail.com>2019-10-08 18:55:03 -0400
committerAlex Goodman <wagoodman@gmail.com>2019-10-08 18:55:03 -0400
commite39e64619140ec72c7cc75d4e0fdb4655fc6fcf6 (patch)
tree6d6dcffa166d2118ff654d82fcfa8afaec776931
parent0e49dd0fec1000fbb3f44f97eb570408ff17ff6c (diff)
adding docker-archive source option
-rw-r--r--README.md13
-rw-r--r--cmd/analyze.go6
-rw-r--r--cmd/build.go2
-rw-r--r--cmd/root.go6
-rw-r--r--dive/get_image_handler.go44
-rw-r--r--dive/get_image_resolver.go51
-rw-r--r--dive/image/docker/archive_resolver.go31
-rw-r--r--dive/image/docker/engine_resolver.go (renamed from dive/image/docker/resolver.go)14
-rw-r--r--dive/image/docker/image_archive.go2
-rw-r--r--dive/image/podman/resolver_linux.go2
-rw-r--r--runtime/options.go4
-rw-r--r--runtime/run.go9
12 files changed, 113 insertions, 71 deletions
diff --git a/README.md b/README.md
index 6520738..4e85f97 100644
--- a/README.md
+++ b/README.md
@@ -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**
diff --git a/cmd/analyze.go b/cmd/analyze.go
index 4e6c27e..3c6f0ab 100644
--- a/cmd/analyze.go
+++ b/cmd/analyze.go
@@ -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,
})
diff --git a/cmd/build.go b/cmd/build.go
index 0cff973..9c65fab 100644
--- a/cmd/build.go
+++ b/cmd/build.go
@@ -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,
diff --git a/cmd/root.go b/cmd/root.go
index 877bca7..c58e46e 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -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.
diff --git a/dive/get_image_handler.go b/dive/get_image_handler.go
deleted file mode 100644
index d8e5cec..0000000
--- a/dive/get_image_handler.go
+++ /dev/null
@@ -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")
-}
diff --git a/dive/get_image_resolver.go b/dive/get_image_resolver.go
new file mode 100644
index 0000000..3e9f9c6
--- /dev/null
+++ b/dive/get_image_resolver.go
@@ -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")
+}
diff --git a/dive/image/docker/archive_resolver.go b/dive/image/docker/archive_resolver.go
new file mode 100644
index 0000000..5cdc923
--- /dev/null
+++ b/dive/image/docker/archive_resolver.go
@@ -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")
+}
diff --git a/dive/image/docker/resolver.go b/dive/image/docker/engine_resolver.go
index 461bbf9..3f91fe0 100644
--- a/dive/image/docker/resolver.go
+++ b/dive/image/docker/engine_resolver.go
@@ -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")
diff --git a/dive/image/docker/image_archive.go b/dive/image/docker/image_archive.go
index dd4b38d..7b2f806 100644
--- a/dive/image/docker/image_archive.go
+++ b/dive/image/docker/image_archive.go
@@ -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
diff --git a/dive/image/podman/resolver_linux.go b/dive/image/podman/resolver_linux.go
index 4dbda15..928bd35 100644
--- a/dive/image/podman/resolver_linux.go
+++ b/dive/image/podman/resolver_linux.go
@@ -13,7 +13,7 @@ import (
type resolver struct{}
-func NewResolver() *resolver {
+func NewResolverFromEngine() *resolver {
return &resolver{}
}
diff --git a/runtime/options.go b/runtime/options.go
index a2b124e..5554a22 100644
--- a/runtime/options.go
+++ b/runtime/options.go
@@ -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
diff --git a/runtime/run.go b/runtime/run.go
index 024b4ca..a55f583 100644
--- a/runtime/run.go
+++ b/runtime/run.go
@@ -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)