adding docker-archive source option
This commit is contained in:
parent
0e49dd0fec
commit
e39e646191
13
README.md
13
README.md
@ -61,14 +61,17 @@ command.
|
|||||||
**CI Integration**
|
**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.
|
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**
|
**With Multiple Image Sources and Container Engines Supported**
|
||||||
- Docker (default)
|
With the `--source` option, you can select where to fetch the container image from:
|
||||||
- Podman (linux only)
|
|
||||||
|
|
||||||
```bash
|
```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
|
## Installation
|
||||||
|
|
||||||
**Ubuntu/Debian**
|
**Ubuntu/Debian**
|
||||||
|
@ -39,7 +39,7 @@ func doAnalyzeCmd(cmd *cobra.Command, args []string) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := cmd.PersistentFlags().GetString("engine")
|
engine, err := cmd.PersistentFlags().GetString("source")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("unable to determine engine: %v\n", err)
|
fmt.Printf("unable to determine engine: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -47,8 +47,8 @@ func doAnalyzeCmd(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
runtime.Run(runtime.Options{
|
runtime.Run(runtime.Options{
|
||||||
Ci: isCi,
|
Ci: isCi,
|
||||||
Engine: dive.GetEngine(engine),
|
Source: dive.ParseImageSource(engine),
|
||||||
ImageId: userImage,
|
Image: userImage,
|
||||||
ExportFile: exportFile,
|
ExportFile: exportFile,
|
||||||
CiConfig: ciConfig,
|
CiConfig: ciConfig,
|
||||||
})
|
})
|
||||||
|
@ -29,7 +29,7 @@ func doBuildCmd(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
runtime.Run(runtime.Options{
|
runtime.Run(runtime.Options{
|
||||||
Ci: isCi,
|
Ci: isCi,
|
||||||
Engine: dive.GetEngine(engine),
|
Source: dive.ParseImageSource(engine),
|
||||||
BuildArgs: args,
|
BuildArgs: args,
|
||||||
ExportFile: exportFile,
|
ExportFile: exportFile,
|
||||||
CiConfig: ciConfig,
|
CiConfig: ciConfig,
|
||||||
|
@ -46,6 +46,7 @@ func init() {
|
|||||||
|
|
||||||
func initCli() {
|
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().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.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().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.")
|
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.
|
// initConfig reads in config file and ENV variables if set.
|
||||||
|
@ -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")
|
|
||||||
}
|
|
51
dive/get_image_resolver.go
Normal file
51
dive/get_image_resolver.go
Normal 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")
|
||||||
|
}
|
31
dive/image/docker/archive_resolver.go
Normal file
31
dive/image/docker/archive_resolver.go
Normal 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")
|
||||||
|
}
|
@ -13,13 +13,13 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type resolver struct{}
|
type engineResolver struct{}
|
||||||
|
|
||||||
func NewResolver() *resolver {
|
func NewResolverFromEngine() *engineResolver {
|
||||||
return &resolver{}
|
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)
|
reader, err := r.fetchArchive(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -34,7 +34,7 @@ func (r *resolver) Fetch(id string) (*image.Image, error) {
|
|||||||
return img.ToImage()
|
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)
|
id, err := buildImageFromCli(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -42,11 +42,11 @@ func (r *resolver) Build(args []string) (*image.Image, error) {
|
|||||||
return r.Fetch(id)
|
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 err error
|
||||||
var dockerClient *client.Client
|
var dockerClient *client.Client
|
||||||
|
|
||||||
// pull the resolver if it does not exist
|
// pull the engineResolver if it does not exist
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
host := os.Getenv("DOCKER_HOST")
|
host := os.Getenv("DOCKER_HOST")
|
@ -149,7 +149,7 @@ func (img *ImageArchive) ToImage() (*image.Image, error) {
|
|||||||
// build the layers array
|
// build the layers array
|
||||||
layers := make([]*image.Layer, 0)
|
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)
|
// as you iterate chronologically through history (ignoring history items that have no layer contents)
|
||||||
// Note: history is not required metadata in a docker image!
|
// Note: history is not required metadata in a docker image!
|
||||||
histIdx := 0
|
histIdx := 0
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
type resolver struct{}
|
type resolver struct{}
|
||||||
|
|
||||||
func NewResolver() *resolver {
|
func NewResolverFromEngine() *resolver {
|
||||||
return &resolver{}
|
return &resolver{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Ci bool
|
Ci bool
|
||||||
ImageId string
|
Image string
|
||||||
Engine dive.Engine
|
Source dive.ImageSource
|
||||||
ExportFile string
|
ExportFile string
|
||||||
CiConfig *viper.Viper
|
CiConfig *viper.Viper
|
||||||
BuildArgs []string
|
BuildArgs []string
|
||||||
|
@ -25,6 +25,8 @@ func runCi(analysis *image.AnalysisResult, options Options) {
|
|||||||
evaluator := ci.NewCiEvaluator(options.CiConfig)
|
evaluator := ci.NewCiEvaluator(options.CiConfig)
|
||||||
|
|
||||||
pass := evaluator.Evaluate(analysis)
|
pass := evaluator.Evaluate(analysis)
|
||||||
|
|
||||||
|
// todo: report should return a string?
|
||||||
evaluator.Report()
|
evaluator.Report()
|
||||||
|
|
||||||
if pass {
|
if pass {
|
||||||
@ -33,6 +35,9 @@ func runCi(analysis *image.AnalysisResult, options Options) {
|
|||||||
os.Exit(1)
|
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) {
|
func Run(options Options) {
|
||||||
var err error
|
var err error
|
||||||
doExport := options.ExportFile != ""
|
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
|
// 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 {
|
if err != nil {
|
||||||
fmt.Printf("cannot determine image provider: %v\n", err)
|
fmt.Printf("cannot determine image provider: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -60,7 +65,7 @@ func Run(options Options) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)")
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("cannot fetch image: %v\n", err)
|
fmt.Printf("cannot fetch image: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user