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**
|
||||
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**
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
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")
|
@ -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
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
|
||||
type resolver struct{}
|
||||
|
||||
func NewResolver() *resolver {
|
||||
func NewResolverFromEngine() *resolver {
|
||||
return &resolver{}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user