1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package server
18
19import (
20	"fmt"
21	"path"
22	"path/filepath"
23	"strconv"
24	"strings"
25
26	"github.com/BurntSushi/toml"
27	runhcsoptions "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
28	"github.com/containerd/containerd"
29	"github.com/containerd/containerd/containers"
30	"github.com/containerd/containerd/plugin"
31	"github.com/containerd/containerd/reference/docker"
32	"github.com/containerd/containerd/runtime/linux/runctypes"
33	runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
34	"github.com/containerd/typeurl"
35	imagedigest "github.com/opencontainers/go-digest"
36	"github.com/pkg/errors"
37	"golang.org/x/net/context"
38	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
39
40	criconfig "github.com/containerd/containerd/pkg/cri/config"
41	"github.com/containerd/containerd/pkg/cri/store"
42	containerstore "github.com/containerd/containerd/pkg/cri/store/container"
43	imagestore "github.com/containerd/containerd/pkg/cri/store/image"
44	sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
45	runtimeoptions "github.com/containerd/containerd/pkg/runtimeoptions/v1"
46)
47
48const (
49	// errorStartReason is the exit reason when fails to start container.
50	errorStartReason = "StartError"
51	// errorStartExitCode is the exit code when fails to start container.
52	// 128 is the same with Docker's behavior.
53	// TODO(windows): Figure out what should be used for windows.
54	errorStartExitCode = 128
55	// completeExitReason is the exit reason when container exits with code 0.
56	completeExitReason = "Completed"
57	// errorExitReason is the exit reason when container exits with code non-zero.
58	errorExitReason = "Error"
59	// oomExitReason is the exit reason when process in container is oom killed.
60	oomExitReason = "OOMKilled"
61
62	// sandboxesDir contains all sandbox root. A sandbox root is the running
63	// directory of the sandbox, all files created for the sandbox will be
64	// placed under this directory.
65	sandboxesDir = "sandboxes"
66	// containersDir contains all container root.
67	containersDir = "containers"
68	// Delimiter used to construct container/sandbox names.
69	nameDelimiter = "_"
70
71	// criContainerdPrefix is common prefix for cri-containerd
72	criContainerdPrefix = "io.cri-containerd"
73	// containerKindLabel is a label key indicating container is sandbox container or application container
74	containerKindLabel = criContainerdPrefix + ".kind"
75	// containerKindSandbox is a label value indicating container is sandbox container
76	containerKindSandbox = "sandbox"
77	// containerKindContainer is a label value indicating container is application container
78	containerKindContainer = "container"
79	// imageLabelKey is the label key indicating the image is managed by cri plugin.
80	imageLabelKey = criContainerdPrefix + ".image"
81	// imageLabelValue is the label value indicating the image is managed by cri plugin.
82	imageLabelValue = "managed"
83	// sandboxMetadataExtension is an extension name that identify metadata of sandbox in CreateContainerRequest
84	sandboxMetadataExtension = criContainerdPrefix + ".sandbox.metadata"
85	// containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest
86	containerMetadataExtension = criContainerdPrefix + ".container.metadata"
87
88	// defaultIfName is the default network interface for the pods
89	defaultIfName = "eth0"
90
91	// runtimeRunhcsV1 is the runtime type for runhcs.
92	runtimeRunhcsV1 = "io.containerd.runhcs.v1"
93)
94
95// makeSandboxName generates sandbox name from sandbox metadata. The name
96// generated is unique as long as sandbox metadata is unique.
97func makeSandboxName(s *runtime.PodSandboxMetadata) string {
98	return strings.Join([]string{
99		s.Name,                       // 0
100		s.Namespace,                  // 1
101		s.Uid,                        // 2
102		fmt.Sprintf("%d", s.Attempt), // 3
103	}, nameDelimiter)
104}
105
106// makeContainerName generates container name from sandbox and container metadata.
107// The name generated is unique as long as the sandbox container combination is
108// unique.
109func makeContainerName(c *runtime.ContainerMetadata, s *runtime.PodSandboxMetadata) string {
110	return strings.Join([]string{
111		c.Name,                       // 0
112		s.Name,                       // 1: pod name
113		s.Namespace,                  // 2: pod namespace
114		s.Uid,                        // 3: pod uid
115		fmt.Sprintf("%d", c.Attempt), // 4
116	}, nameDelimiter)
117}
118
119// getSandboxRootDir returns the root directory for managing sandbox files,
120// e.g. hosts files.
121func (c *criService) getSandboxRootDir(id string) string {
122	return filepath.Join(c.config.RootDir, sandboxesDir, id)
123}
124
125// getVolatileSandboxRootDir returns the root directory for managing volatile sandbox files,
126// e.g. named pipes.
127func (c *criService) getVolatileSandboxRootDir(id string) string {
128	return filepath.Join(c.config.StateDir, sandboxesDir, id)
129}
130
131// getContainerRootDir returns the root directory for managing container files,
132// e.g. state checkpoint.
133func (c *criService) getContainerRootDir(id string) string {
134	return filepath.Join(c.config.RootDir, containersDir, id)
135}
136
137// getVolatileContainerRootDir returns the root directory for managing volatile container files,
138// e.g. named pipes.
139func (c *criService) getVolatileContainerRootDir(id string) string {
140	return filepath.Join(c.config.StateDir, containersDir, id)
141}
142
143// criContainerStateToString formats CRI container state to string.
144func criContainerStateToString(state runtime.ContainerState) string {
145	return runtime.ContainerState_name[int32(state)]
146}
147
148// getRepoDigestAngTag returns image repoDigest and repoTag of the named image reference.
149func getRepoDigestAndTag(namedRef docker.Named, digest imagedigest.Digest, schema1 bool) (string, string) {
150	var repoTag, repoDigest string
151	if _, ok := namedRef.(docker.NamedTagged); ok {
152		repoTag = namedRef.String()
153	}
154	if _, ok := namedRef.(docker.Canonical); ok {
155		repoDigest = namedRef.String()
156	} else if !schema1 {
157		// digest is not actual repo digest for schema1 image.
158		repoDigest = namedRef.Name() + "@" + digest.String()
159	}
160	return repoDigest, repoTag
161}
162
163// localResolve resolves image reference locally and returns corresponding image metadata. It
164// returns store.ErrNotExist if the reference doesn't exist.
165func (c *criService) localResolve(refOrID string) (imagestore.Image, error) {
166	getImageID := func(refOrId string) string {
167		if _, err := imagedigest.Parse(refOrID); err == nil {
168			return refOrID
169		}
170		return func(ref string) string {
171			// ref is not image id, try to resolve it locally.
172			// TODO(random-liu): Handle this error better for debugging.
173			normalized, err := docker.ParseDockerRef(ref)
174			if err != nil {
175				return ""
176			}
177			id, err := c.imageStore.Resolve(normalized.String())
178			if err != nil {
179				return ""
180			}
181			return id
182		}(refOrID)
183	}
184
185	imageID := getImageID(refOrID)
186	if imageID == "" {
187		// Try to treat ref as imageID
188		imageID = refOrID
189	}
190	return c.imageStore.Get(imageID)
191}
192
193// toContainerdImage converts an image object in image store to containerd image handler.
194func (c *criService) toContainerdImage(ctx context.Context, image imagestore.Image) (containerd.Image, error) {
195	// image should always have at least one reference.
196	if len(image.References) == 0 {
197		return nil, errors.Errorf("invalid image with no reference %q", image.ID)
198	}
199	return c.client.GetImage(ctx, image.References[0])
200}
201
202// getUserFromImage gets uid or user name of the image user.
203// If user is numeric, it will be treated as uid; or else, it is treated as user name.
204func getUserFromImage(user string) (*int64, string) {
205	// return both empty if user is not specified in the image.
206	if user == "" {
207		return nil, ""
208	}
209	// split instances where the id may contain user:group
210	user = strings.Split(user, ":")[0]
211	// user could be either uid or user name. Try to interpret as numeric uid.
212	uid, err := strconv.ParseInt(user, 10, 64)
213	if err != nil {
214		// If user is non numeric, assume it's user name.
215		return nil, user
216	}
217	// If user is a numeric uid.
218	return &uid, ""
219}
220
221// ensureImageExists returns corresponding metadata of the image reference, if image is not
222// pulled yet, the function will pull the image.
223func (c *criService) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig) (*imagestore.Image, error) {
224	image, err := c.localResolve(ref)
225	if err != nil && err != store.ErrNotExist {
226		return nil, errors.Wrapf(err, "failed to get image %q", ref)
227	}
228	if err == nil {
229		return &image, nil
230	}
231	// Pull image to ensure the image exists
232	resp, err := c.PullImage(ctx, &runtime.PullImageRequest{Image: &runtime.ImageSpec{Image: ref}, SandboxConfig: config})
233	if err != nil {
234		return nil, errors.Wrapf(err, "failed to pull image %q", ref)
235	}
236	imageID := resp.GetImageRef()
237	newImage, err := c.imageStore.Get(imageID)
238	if err != nil {
239		// It's still possible that someone removed the image right after it is pulled.
240		return nil, errors.Wrapf(err, "failed to get image %q after pulling", imageID)
241	}
242	return &newImage, nil
243}
244
245// isInCRIMounts checks whether a destination is in CRI mount list.
246func isInCRIMounts(dst string, mounts []*runtime.Mount) bool {
247	for _, m := range mounts {
248		if filepath.Clean(m.ContainerPath) == filepath.Clean(dst) {
249			return true
250		}
251	}
252	return false
253}
254
255// filterLabel returns a label filter. Use `%q` here because containerd
256// filter needs extra quote to work properly.
257func filterLabel(k, v string) string {
258	return fmt.Sprintf("labels.%q==%q", k, v)
259}
260
261// buildLabel builds the labels from config to be passed to containerd
262func buildLabels(configLabels map[string]string, containerType string) map[string]string {
263	labels := make(map[string]string)
264	for k, v := range configLabels {
265		labels[k] = v
266	}
267	labels[containerKindLabel] = containerType
268	return labels
269}
270
271// toRuntimeAuthConfig converts cri plugin auth config to runtime auth config.
272func toRuntimeAuthConfig(a criconfig.AuthConfig) *runtime.AuthConfig {
273	return &runtime.AuthConfig{
274		Username:      a.Username,
275		Password:      a.Password,
276		Auth:          a.Auth,
277		IdentityToken: a.IdentityToken,
278	}
279}
280
281// parseImageReferences parses a list of arbitrary image references and returns
282// the repotags and repodigests
283func parseImageReferences(refs []string) ([]string, []string) {
284	var tags, digests []string
285	for _, ref := range refs {
286		parsed, err := docker.ParseAnyReference(ref)
287		if err != nil {
288			continue
289		}
290		if _, ok := parsed.(docker.Canonical); ok {
291			digests = append(digests, parsed.String())
292		} else if _, ok := parsed.(docker.Tagged); ok {
293			tags = append(tags, parsed.String())
294		}
295	}
296	return tags, digests
297}
298
299// generateRuntimeOptions generates runtime options from cri plugin config.
300func generateRuntimeOptions(r criconfig.Runtime, c criconfig.Config) (interface{}, error) {
301	if r.Options == nil {
302		if r.Type != plugin.RuntimeLinuxV1 {
303			return nil, nil
304		}
305		// This is a legacy config, generate runctypes.RuncOptions.
306		return &runctypes.RuncOptions{
307			Runtime:       r.Engine,
308			RuntimeRoot:   r.Root,
309			SystemdCgroup: c.SystemdCgroup,
310		}, nil
311	}
312	options := getRuntimeOptionsType(r.Type)
313	if err := toml.PrimitiveDecode(*r.Options, options); err != nil {
314		return nil, err
315	}
316	return options, nil
317}
318
319// getRuntimeOptionsType gets empty runtime options by the runtime type name.
320func getRuntimeOptionsType(t string) interface{} {
321	switch t {
322	case plugin.RuntimeRuncV1:
323		fallthrough
324	case plugin.RuntimeRuncV2:
325		return &runcoptions.Options{}
326	case plugin.RuntimeLinuxV1:
327		return &runctypes.RuncOptions{}
328	case runtimeRunhcsV1:
329		return &runhcsoptions.Options{}
330	default:
331		return &runtimeoptions.Options{}
332	}
333}
334
335// getRuntimeOptions get runtime options from container metadata.
336func getRuntimeOptions(c containers.Container) (interface{}, error) {
337	if c.Runtime.Options == nil {
338		return nil, nil
339	}
340	opts, err := typeurl.UnmarshalAny(c.Runtime.Options)
341	if err != nil {
342		return nil, err
343	}
344	return opts, nil
345}
346
347const (
348	// unknownExitCode is the exit code when exit reason is unknown.
349	unknownExitCode = 255
350	// unknownExitReason is the exit reason when exit reason is unknown.
351	unknownExitReason = "Unknown"
352)
353
354// unknownContainerStatus returns the default container status when its status is unknown.
355func unknownContainerStatus() containerstore.Status {
356	return containerstore.Status{
357		CreatedAt:  0,
358		StartedAt:  0,
359		FinishedAt: 0,
360		ExitCode:   unknownExitCode,
361		Reason:     unknownExitReason,
362		Unknown:    true,
363	}
364}
365
366// unknownSandboxStatus returns the default sandbox status when its status is unknown.
367func unknownSandboxStatus() sandboxstore.Status {
368	return sandboxstore.Status{
369		State: sandboxstore.StateUnknown,
370	}
371}
372
373// getPassthroughAnnotations filters requested pod annotations by comparing
374// against permitted annotations for the given runtime.
375func getPassthroughAnnotations(podAnnotations map[string]string,
376	runtimePodAnnotations []string) (passthroughAnnotations map[string]string) {
377	passthroughAnnotations = make(map[string]string)
378
379	for podAnnotationKey, podAnnotationValue := range podAnnotations {
380		for _, pattern := range runtimePodAnnotations {
381			// Use path.Match instead of filepath.Match here.
382			// filepath.Match treated `\\` as path separator
383			// on windows, which is not what we want.
384			if ok, _ := path.Match(pattern, podAnnotationKey); ok {
385				passthroughAnnotations[podAnnotationKey] = podAnnotationValue
386			}
387		}
388	}
389	return passthroughAnnotations
390}
391