summaryrefslogtreecommitdiff
path: root/libpod/image/image.go
blob: a3f0bce836c325723df7bfe96f2e1edea56a607f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package image

import (
	"fmt"
	"io"
	"os"

	"github.com/containers/image/docker/reference"
	"github.com/containers/storage"
	"github.com/pkg/errors"
	"github.com/projectatomic/libpod/libpod"
	"github.com/projectatomic/libpod/pkg/inspect"
)

// Image is the primary struct for dealing with images
// It is still very much a work in progress
type Image struct {
	inspect.ImageData
	InputName string
	Local     bool
	runtime   *libpod.Runtime
	image     *storage.Image
}

// NewFromLocal creates a new image object that is intended
// to only deal with local images already in the store (or
// its aliases)
func NewFromLocal(name string, runtime *libpod.Runtime) (Image, error) {
	image := Image{
		InputName: name,
		Local:     true,
		runtime:   runtime,
	}
	localImage, err := image.getLocalImage()
	if err != nil {
		return Image{}, err
	}
	image.image = localImage
	return image, nil
}

// New creates a new image object where the image could be local
// or remote
func New(name string, runtime *libpod.Runtime) (Image, error) {
	// We don't know if the image is local or not ... check local first
	newImage := Image{
		InputName: name,
		Local:     false,
		runtime:   runtime,
	}
	localImage, err := newImage.getLocalImage()
	if err == nil {
		newImage.Local = true
		newImage.image = localImage
		return newImage, nil
	}

	// The image is not local
	pullNames, err := newImage.createNamesToPull()
	if err != nil {
		return newImage, err
	}
	if len(pullNames) == 0 {
		return newImage, errors.Errorf("unable to pull %s", newImage.InputName)
	}
	var writer io.Writer
	writer = os.Stderr
	for _, p := range pullNames {
		_, err := newImage.pull(p, writer, runtime)
		if err == nil {
			newImage.InputName = p
			img, err := newImage.getLocalImage()
			newImage.image = img
			return newImage, err
		}
	}
	return newImage, errors.Errorf("unable to find %s", name)
}

// getLocalImage resolves an unknown input describing an image and
// returns a storage.Image or an error. It is used by NewFromLocal.
func (i *Image) getLocalImage() (*storage.Image, error) {
	imageError := fmt.Sprintf("unable to find '%s' in local storage\n", i.InputName)
	if i.InputName == "" {
		return nil, errors.Errorf("input name is blank")
	}
	var taggedName string
	img, err := i.runtime.GetImage(i.InputName)
	if err == nil {
		return img, err
	}

	// container-storage wasn't able to find it in its current form
	// check if the input name has a tag, and if not, run it through
	// again
	decomposedImage, err := decompose(i.InputName)
	if err != nil {
		return nil, err
	}
	// the inputname isn't tagged, so we assume latest and try again
	if !decomposedImage.isTagged {
		taggedName = fmt.Sprintf("%s:latest", i.InputName)
		img, err = i.runtime.GetImage(taggedName)
		if err == nil {
			return img, nil
		}
	}
	hasReg, err := i.hasRegistry()
	if err != nil {
		return nil, errors.Wrapf(err, imageError)
	}

	// if the input name has a registry in it, the image isnt here
	if hasReg {
		return nil, errors.Errorf("%s", imageError)
	}

	// grab all the local images
	images, err := i.runtime.GetImages(&libpod.ImageFilterParams{})
	if err != nil {
		return nil, err
	}

	// check the repotags of all images for a match
	repoImage, err := findImageInRepotags(decomposedImage, images)
	if err == nil {
		return repoImage, nil
	}

	return nil, errors.Errorf("%s", imageError)
}

// hasRegistry returns a bool/err response if the image has a registry in its
// name
func (i *Image) hasRegistry() (bool, error) {
	imgRef, err := reference.Parse(i.InputName)
	if err != nil {
		return false, err
	}
	registry := reference.Domain(imgRef.(reference.Named))
	if registry != "" {
		return true, nil
	}
	return false, nil
}

// ID returns the image ID as a string
func (i *Image) ID() string {
	return i.image.ID
}

// createNamesToPull looks at a decomposed image and determines the possible
// images names to try pulling in combination with the registries.conf file as well
func (i *Image) createNamesToPull() ([]string, error) {
	var pullNames []string
	decomposedImage, err := decompose(i.InputName)
	if err != nil {
		return nil, err
	}

	if decomposedImage.hasRegistry {
		pullNames = append(pullNames, i.InputName)
	} else {
		registries, err := libpod.GetRegistries()
		if err != nil {
			return nil, err
		}
		for _, registry := range registries {
			decomposedImage.registry = registry
			pullNames = append(pullNames, decomposedImage.assemble())
		}
	}
	return pullNames, nil
}

// pull is a temporary function for stage1 to be able to pull images during the image
// resolution tests.  it will be replaced in stage2 with a more robust function.
func (i *Image) pull(name string, writer io.Writer, r *libpod.Runtime) (string, error) {
	options := libpod.CopyOptions{
		Writer:              writer,
		SignaturePolicyPath: r.GetConfig().SignaturePolicyPath,
	}
	return i.runtime.PullImage(name, options)
}

// Remove an image
// This function is only complete enough for the stage 1 tests.
func (i *Image) Remove(force bool) error {
	_, err := i.runtime.RemoveImage(i.image, force)
	return err
}