containerd/oci/spec.go
Danny Canter 0a6b8f0ee0 OCI: Add From/ToProto helpers for Descriptor
Helpers to convert from the OCI image specs [Descriptor] to its protobuf
structure for Descriptor and vice-versa appear three times. It seems sane
to just expose this facility in /oci.

Signed-off-by: Danny Canter <danny@dcantah.dev>
2023-06-28 12:16:20 -07:00

248 lines
5.9 KiB
Go

/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package oci
import (
"context"
"path/filepath"
"runtime"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/platforms"
)
const (
rwm = "rwm"
defaultRootfsPath = "rootfs"
)
var (
defaultUnixEnv = []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
}
)
// Spec is a type alias to the OCI runtime spec to allow third part SpecOpts
// to be created without the "issues" with go vendoring and package imports
type Spec = specs.Spec
// GenerateSpec will generate a default spec from the provided image
// for use as a containerd container
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
return GenerateSpecWithPlatform(ctx, client, platforms.DefaultString(), c, opts...)
}
// GenerateSpecWithPlatform will generate a default spec from the provided image
// for use as a containerd container in the platform requested.
func GenerateSpecWithPlatform(ctx context.Context, client Client, platform string, c *containers.Container, opts ...SpecOpts) (*Spec, error) {
var s Spec
if err := generateDefaultSpecWithPlatform(ctx, platform, c.ID, &s); err != nil {
return nil, err
}
return &s, ApplyOpts(ctx, client, c, &s, opts...)
}
func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s *Spec) error {
plat, err := platforms.Parse(platform)
if err != nil {
return err
}
switch plat.OS {
case "windows":
err = populateDefaultWindowsSpec(ctx, s, id)
case "darwin":
err = populateDefaultDarwinSpec(s)
default:
err = populateDefaultUnixSpec(ctx, s, id)
if err == nil && runtime.GOOS == "windows" {
// To run LCOW we have a Linux and Windows section. Add an empty one now.
s.Windows = &specs.Windows{}
}
}
return err
}
// ApplyOpts applies the options to the given spec, injecting data from the
// context, client and container instance.
func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *Spec, opts ...SpecOpts) error {
for _, o := range opts {
if err := o(ctx, client, c, s); err != nil {
return err
}
}
return nil
}
func defaultUnixCaps() []string {
return []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
}
}
func defaultUnixNamespaces() []specs.LinuxNamespace {
return []specs.LinuxNamespace{
{
Type: specs.PIDNamespace,
},
{
Type: specs.IPCNamespace,
},
{
Type: specs.UTSNamespace,
},
{
Type: specs.MountNamespace,
},
{
Type: specs.NetworkNamespace,
},
}
}
func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
*s = Spec{
Version: specs.Version,
Root: &specs.Root{
Path: defaultRootfsPath,
},
Process: &specs.Process{
Cwd: "/",
NoNewPrivileges: true,
User: specs.User{
UID: 0,
GID: 0,
},
Capabilities: &specs.LinuxCapabilities{
Bounding: defaultUnixCaps(),
Permitted: defaultUnixCaps(),
Effective: defaultUnixCaps(),
},
Rlimits: []specs.POSIXRlimit{
{
Type: "RLIMIT_NOFILE",
Hard: uint64(1024),
Soft: uint64(1024),
},
},
},
Linux: &specs.Linux{
MaskedPaths: []string{
"/proc/acpi",
"/proc/asound",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi",
},
ReadonlyPaths: []string{
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
},
CgroupsPath: filepath.Join("/", ns, id),
Resources: &specs.LinuxResources{
Devices: []specs.LinuxDeviceCgroup{
{
Allow: false,
Access: rwm,
},
},
},
Namespaces: defaultUnixNamespaces(),
},
}
s.Mounts = defaultMounts()
return nil
}
func populateDefaultWindowsSpec(ctx context.Context, s *Spec, id string) error {
*s = Spec{
Version: specs.Version,
Root: &specs.Root{},
Process: &specs.Process{
Cwd: `C:\`,
},
Windows: &specs.Windows{},
}
return nil
}
func populateDefaultDarwinSpec(s *Spec) error {
*s = Spec{
Version: specs.Version,
Root: &specs.Root{},
Process: &specs.Process{Cwd: "/"},
}
return nil
}
// DescriptorFromProto converts containerds protobuf [types.Descriptor]
// to the OCI image specs [ocispec.Descriptor].
func DescriptorFromProto(d *types.Descriptor) ocispec.Descriptor {
return ocispec.Descriptor{
MediaType: d.MediaType,
Digest: digest.Digest(d.Digest),
Size: d.Size,
Annotations: d.Annotations,
}
}
// DescriptorToProto converts the OCI image specs [ocispec.Descriptor]
// to containerds protobuf [types.Descriptor].
func DescriptorToProto(d ocispec.Descriptor) *types.Descriptor {
return &types.Descriptor{
MediaType: d.MediaType,
Digest: d.Digest.String(),
Size: d.Size,
Annotations: d.Annotations,
}
}