...

Source file src/github.com/google/go-containerregistry/pkg/v1/daemon/image.go

Documentation: github.com/google/go-containerregistry/pkg/v1/daemon

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package daemon
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"io"
    21  	"sync"
    22  	"time"
    23  
    24  	api "github.com/docker/docker/api/types"
    25  	"github.com/docker/docker/api/types/container"
    26  
    27  	"github.com/google/go-containerregistry/pkg/name"
    28  	v1 "github.com/google/go-containerregistry/pkg/v1"
    29  	"github.com/google/go-containerregistry/pkg/v1/tarball"
    30  	"github.com/google/go-containerregistry/pkg/v1/types"
    31  )
    32  
    33  type image struct {
    34  	ref          name.Reference
    35  	opener       *imageOpener
    36  	tarballImage v1.Image
    37  	computed     bool
    38  	id           *v1.Hash
    39  	configFile   *v1.ConfigFile
    40  
    41  	once sync.Once
    42  	err  error
    43  }
    44  
    45  type imageOpener struct {
    46  	ref name.Reference
    47  	ctx context.Context
    48  
    49  	buffered bool
    50  	client   Client
    51  
    52  	once  sync.Once
    53  	bytes []byte
    54  	err   error
    55  }
    56  
    57  func (i *imageOpener) saveImage() (io.ReadCloser, error) {
    58  	return i.client.ImageSave(i.ctx, []string{i.ref.Name()})
    59  }
    60  
    61  func (i *imageOpener) bufferedOpener() (io.ReadCloser, error) {
    62  	// Store the tarball in memory and return a new reader into the bytes each time we need to access something.
    63  	i.once.Do(func() {
    64  		i.bytes, i.err = func() ([]byte, error) {
    65  			rc, err := i.saveImage()
    66  			if err != nil {
    67  				return nil, err
    68  			}
    69  			defer rc.Close()
    70  
    71  			return io.ReadAll(rc)
    72  		}()
    73  	})
    74  
    75  	// Wrap the bytes in a ReadCloser so it looks like an opened file.
    76  	return io.NopCloser(bytes.NewReader(i.bytes)), i.err
    77  }
    78  
    79  func (i *imageOpener) opener() tarball.Opener {
    80  	if i.buffered {
    81  		return i.bufferedOpener
    82  	}
    83  
    84  	// To avoid storing the tarball in memory, do a save every time we need to access something.
    85  	return i.saveImage
    86  }
    87  
    88  // Image provides access to an image reference from the Docker daemon,
    89  // applying functional options to the underlying imageOpener before
    90  // resolving the reference into a v1.Image.
    91  func Image(ref name.Reference, options ...Option) (v1.Image, error) {
    92  	o, err := makeOptions(options...)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	i := &imageOpener{
    98  		ref:      ref,
    99  		buffered: o.buffered,
   100  		client:   o.client,
   101  		ctx:      o.ctx,
   102  	}
   103  
   104  	img := &image{
   105  		ref:    ref,
   106  		opener: i,
   107  	}
   108  
   109  	// Eagerly fetch Image ID to ensure it actually exists.
   110  	// https://github.com/google/go-containerregistry/issues/1186
   111  	id, err := img.ConfigName()
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	img.id = &id
   116  
   117  	return img, nil
   118  }
   119  
   120  func (i *image) initialize() error {
   121  	// Don't re-initialize tarball if already initialized.
   122  	if i.tarballImage == nil {
   123  		i.once.Do(func() {
   124  			i.tarballImage, i.err = tarball.Image(i.opener.opener(), nil)
   125  		})
   126  	}
   127  	return i.err
   128  }
   129  
   130  func (i *image) compute() error {
   131  	// Don't re-compute if already computed.
   132  	if i.computed {
   133  		return nil
   134  	}
   135  
   136  	inspect, _, err := i.opener.client.ImageInspectWithRaw(i.opener.ctx, i.ref.String())
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	configFile, err := i.computeConfigFile(inspect)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	i.configFile = configFile
   147  	i.computed = true
   148  
   149  	return nil
   150  }
   151  
   152  func (i *image) Layers() ([]v1.Layer, error) {
   153  	if err := i.initialize(); err != nil {
   154  		return nil, err
   155  	}
   156  	return i.tarballImage.Layers()
   157  }
   158  
   159  func (i *image) MediaType() (types.MediaType, error) {
   160  	if err := i.initialize(); err != nil {
   161  		return "", err
   162  	}
   163  	return i.tarballImage.MediaType()
   164  }
   165  
   166  func (i *image) Size() (int64, error) {
   167  	if err := i.initialize(); err != nil {
   168  		return 0, err
   169  	}
   170  	return i.tarballImage.Size()
   171  }
   172  
   173  func (i *image) ConfigName() (v1.Hash, error) {
   174  	if i.id != nil {
   175  		return *i.id, nil
   176  	}
   177  	res, _, err := i.opener.client.ImageInspectWithRaw(i.opener.ctx, i.ref.String())
   178  	if err != nil {
   179  		return v1.Hash{}, err
   180  	}
   181  	return v1.NewHash(res.ID)
   182  }
   183  
   184  func (i *image) ConfigFile() (*v1.ConfigFile, error) {
   185  	if err := i.compute(); err != nil {
   186  		return nil, err
   187  	}
   188  	return i.configFile.DeepCopy(), nil
   189  }
   190  
   191  func (i *image) RawConfigFile() ([]byte, error) {
   192  	if err := i.initialize(); err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	// RawConfigFile cannot be generated from "docker inspect" because Docker Engine API returns serialized data,
   197  	// and formatting information of the raw config such as indent and prefix will be lost.
   198  	return i.tarballImage.RawConfigFile()
   199  }
   200  
   201  func (i *image) Digest() (v1.Hash, error) {
   202  	if err := i.initialize(); err != nil {
   203  		return v1.Hash{}, err
   204  	}
   205  	return i.tarballImage.Digest()
   206  }
   207  
   208  func (i *image) Manifest() (*v1.Manifest, error) {
   209  	if err := i.initialize(); err != nil {
   210  		return nil, err
   211  	}
   212  	return i.tarballImage.Manifest()
   213  }
   214  
   215  func (i *image) RawManifest() ([]byte, error) {
   216  	if err := i.initialize(); err != nil {
   217  		return nil, err
   218  	}
   219  	return i.tarballImage.RawManifest()
   220  }
   221  
   222  func (i *image) LayerByDigest(h v1.Hash) (v1.Layer, error) {
   223  	if err := i.initialize(); err != nil {
   224  		return nil, err
   225  	}
   226  	return i.tarballImage.LayerByDigest(h)
   227  }
   228  
   229  func (i *image) LayerByDiffID(h v1.Hash) (v1.Layer, error) {
   230  	if err := i.initialize(); err != nil {
   231  		return nil, err
   232  	}
   233  	return i.tarballImage.LayerByDiffID(h)
   234  }
   235  
   236  func (i *image) configHistory(author string) ([]v1.History, error) {
   237  	historyItems, err := i.opener.client.ImageHistory(i.opener.ctx, i.ref.String())
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	history := make([]v1.History, len(historyItems))
   243  	for j, h := range historyItems {
   244  		history[j] = v1.History{
   245  			Author: author,
   246  			Created: v1.Time{
   247  				Time: time.Unix(h.Created, 0).UTC(),
   248  			},
   249  			CreatedBy:  h.CreatedBy,
   250  			Comment:    h.Comment,
   251  			EmptyLayer: h.Size == 0,
   252  		}
   253  	}
   254  	return history, nil
   255  }
   256  
   257  func (i *image) diffIDs(rootFS api.RootFS) ([]v1.Hash, error) {
   258  	diffIDs := make([]v1.Hash, len(rootFS.Layers))
   259  	for j, l := range rootFS.Layers {
   260  		h, err := v1.NewHash(l)
   261  		if err != nil {
   262  			return nil, err
   263  		}
   264  		diffIDs[j] = h
   265  	}
   266  	return diffIDs, nil
   267  }
   268  
   269  func (i *image) computeConfigFile(inspect api.ImageInspect) (*v1.ConfigFile, error) {
   270  	diffIDs, err := i.diffIDs(inspect.RootFS)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	history, err := i.configHistory(inspect.Author)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	created, err := time.Parse(time.RFC3339Nano, inspect.Created)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	return &v1.ConfigFile{
   286  		Architecture:  inspect.Architecture,
   287  		Author:        inspect.Author,
   288  		Container:     inspect.Container,
   289  		Created:       v1.Time{Time: created},
   290  		DockerVersion: inspect.DockerVersion,
   291  		History:       history,
   292  		OS:            inspect.Os,
   293  		RootFS: v1.RootFS{
   294  			Type:    inspect.RootFS.Type,
   295  			DiffIDs: diffIDs,
   296  		},
   297  		Config:    i.computeImageConfig(inspect.Config),
   298  		OSVersion: inspect.OsVersion,
   299  	}, nil
   300  }
   301  
   302  func (i *image) computeImageConfig(config *container.Config) v1.Config {
   303  	if config == nil {
   304  		return v1.Config{}
   305  	}
   306  
   307  	c := v1.Config{
   308  		AttachStderr:    config.AttachStderr,
   309  		AttachStdin:     config.AttachStdin,
   310  		AttachStdout:    config.AttachStdout,
   311  		Cmd:             config.Cmd,
   312  		Domainname:      config.Domainname,
   313  		Entrypoint:      config.Entrypoint,
   314  		Env:             config.Env,
   315  		Hostname:        config.Hostname,
   316  		Image:           config.Image,
   317  		Labels:          config.Labels,
   318  		OnBuild:         config.OnBuild,
   319  		OpenStdin:       config.OpenStdin,
   320  		StdinOnce:       config.StdinOnce,
   321  		Tty:             config.Tty,
   322  		User:            config.User,
   323  		Volumes:         config.Volumes,
   324  		WorkingDir:      config.WorkingDir,
   325  		ArgsEscaped:     config.ArgsEscaped,
   326  		NetworkDisabled: config.NetworkDisabled,
   327  		MacAddress:      config.MacAddress,
   328  		StopSignal:      config.StopSignal,
   329  		Shell:           config.Shell,
   330  	}
   331  
   332  	if config.Healthcheck != nil {
   333  		c.Healthcheck = &v1.HealthConfig{
   334  			Test:        config.Healthcheck.Test,
   335  			Interval:    config.Healthcheck.Interval,
   336  			Timeout:     config.Healthcheck.Timeout,
   337  			StartPeriod: config.Healthcheck.StartPeriod,
   338  			Retries:     config.Healthcheck.Retries,
   339  		}
   340  	}
   341  
   342  	if len(config.ExposedPorts) > 0 {
   343  		c.ExposedPorts = map[string]struct{}{}
   344  		for port := range c.ExposedPorts {
   345  			c.ExposedPorts[port] = struct{}{}
   346  		}
   347  	}
   348  
   349  	return c
   350  }
   351  

View as plain text