Merge pull request #8780 from slonopotamus/uncopypaste-read-spec

Uncopypaste parsing of OCI Bundle spec file
This commit is contained in:
Phil Estes 2023-07-11 09:53:00 -04:00 committed by GitHub
commit 34b1653e95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 48 additions and 34 deletions

View File

@ -25,7 +25,8 @@ import (
"syscall" "syscall"
"text/template" "text/template"
specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/containerd/containerd/oci"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -37,7 +38,8 @@ var ociHook = cli.Command{
if err != nil { if err != nil {
return err return err
} }
spec, err := loadSpec(state.Bundle) specFile := filepath.Join(state.Bundle, oci.ConfigFilename)
spec, err := loadSpec(specFile)
if err != nil { if err != nil {
return err return err
} }
@ -56,14 +58,16 @@ var ociHook = cli.Command{
}, },
} }
// hookSpec is a shallow version of [oci.Spec] containing only the
// fields we need for the hook. We use a shallow struct to reduce
// the overhead of unmarshaling.
type hookSpec struct { type hookSpec struct {
Root struct { // Root configures the container's root filesystem.
Path string `json:"path"` Root *specs.Root `json:"root,omitempty"`
} `json:"root"`
} }
func loadSpec(bundle string) (*hookSpec, error) { func loadSpec(path string) (*hookSpec, error) {
f, err := os.Open(filepath.Join(bundle, "config.json")) f, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -18,7 +18,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -35,8 +34,6 @@ import (
) )
const ( const (
ociConfigFilename = "config.json"
failpointPrefixKey = "io.containerd.runtime.v2.shim.failpoint." failpointPrefixKey = "io.containerd.runtime.v2.shim.failpoint."
) )
@ -113,15 +110,10 @@ func newFailpointFromOCIAnnotation() (map[string]*failpoint.Failpoint, error) {
return nil, fmt.Errorf("failed to get current working dir: %w", err) return nil, fmt.Errorf("failed to get current working dir: %w", err)
} }
configPath := filepath.Join(cwd, ociConfigFilename) configPath := filepath.Join(cwd, oci.ConfigFilename)
data, err := os.ReadFile(configPath) spec, err := oci.ReadSpec(configPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read %v: %w", configPath, err) return nil, err
}
var spec oci.Spec
if err := json.Unmarshal(data, &spec); err != nil {
return nil, fmt.Errorf("failed to parse oci.Spec(%v): %w", string(data), err)
} }
res := make(map[string]*failpoint.Failpoint) res := make(map[string]*failpoint.Failpoint)

View File

@ -18,6 +18,8 @@ package oci
import ( import (
"context" "context"
"encoding/json"
"os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -46,6 +48,22 @@ var (
// to be created without the "issues" with go vendoring and package imports // to be created without the "issues" with go vendoring and package imports
type Spec = specs.Spec type Spec = specs.Spec
const ConfigFilename = "config.json"
// ReadSpec deserializes JSON into an OCI runtime Spec from a given path.
func ReadSpec(path string) (*Spec, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var s Spec
if err := json.NewDecoder(f).Decode(&s); err != nil {
return nil, err
}
return &s, nil
}
// GenerateSpec will generate a default spec from the provided image // GenerateSpec will generate a default spec from the provided image
// for use as a containerd container // for use as a containerd container
func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) { func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*Spec, error) {

View File

@ -26,12 +26,11 @@ import (
"github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci"
"github.com/containerd/typeurl/v2" "github.com/containerd/typeurl/v2"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
) )
const configFilename = "config.json"
// LoadBundle loads an existing bundle from disk // LoadBundle loads an existing bundle from disk
func LoadBundle(ctx context.Context, root, id string) (*Bundle, error) { func LoadBundle(ctx context.Context, root, id string) (*Bundle, error) {
ns, err := namespaces.NamespaceRequired(ctx) ns, err := namespaces.NamespaceRequired(ctx)
@ -107,9 +106,10 @@ func NewBundle(ctx context.Context, root, state, id string, spec typeurl.Any) (b
} }
if spec := spec.GetValue(); spec != nil { if spec := spec.GetValue(); spec != nil {
// write the spec to the bundle // write the spec to the bundle
err = os.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666) specPath := filepath.Join(b.Path, oci.ConfigFilename)
err = os.WriteFile(specPath, spec, 0666)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to write %s", configFilename) return nil, fmt.Errorf("failed to write bundle spec: %w", err)
} }
} }
return b, nil return b, nil

View File

@ -32,6 +32,7 @@ import (
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci"
"github.com/containerd/containerd/pkg/process" "github.com/containerd/containerd/pkg/process"
"github.com/containerd/containerd/pkg/schedcore" "github.com/containerd/containerd/pkg/schedcore"
"github.com/containerd/containerd/runtime/v2/runc" "github.com/containerd/containerd/runtime/v2/runc"
@ -58,7 +59,11 @@ var groupLabels = []string{
"io.kubernetes.cri.sandbox-id", "io.kubernetes.cri.sandbox-id",
} }
// spec is a shallow version of [oci.Spec] containing only the
// fields we need for the hook. We use a shallow struct to reduce
// the overhead of unmarshaling.
type spec struct { type spec struct {
// Annotations contains arbitrary metadata for the container.
Annotations map[string]string `json:"annotations,omitempty"` Annotations map[string]string `json:"annotations,omitempty"`
} }
@ -97,7 +102,7 @@ func newCommand(ctx context.Context, id, containerdAddress, containerdTTRPCAddre
} }
func readSpec() (*spec, error) { func readSpec() (*spec, error) {
f, err := os.Open("config.json") f, err := os.Open(oci.ConfigFilename)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -20,29 +20,24 @@ package runc
import ( import (
"context" "context"
"encoding/json"
"os"
"path/filepath" "path/filepath"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/containerd/containerd/oci"
"github.com/opencontainers/runtime-spec/specs-go"
) )
// ShouldKillAllOnExit reads the bundle's OCI spec and returns true if // ShouldKillAllOnExit reads the bundle's OCI spec and returns true if
// there is an error reading the spec or if the container has a private PID namespace // there is an error reading the spec or if the container has a private PID namespace
func ShouldKillAllOnExit(ctx context.Context, bundlePath string) bool { func ShouldKillAllOnExit(ctx context.Context, bundlePath string) bool {
var bundleSpec specs.Spec spec, err := oci.ReadSpec(filepath.Join(bundlePath, oci.ConfigFilename))
bundleConfigContents, err := os.ReadFile(filepath.Join(bundlePath, "config.json"))
if err != nil { if err != nil {
log.G(ctx).WithError(err).Error("shouldKillAllOnExit: failed to read config.json") log.G(ctx).WithError(err).Error("shouldKillAllOnExit: failed to read config.json")
return true return true
} }
if err := json.Unmarshal(bundleConfigContents, &bundleSpec); err != nil {
log.G(ctx).WithError(err).Error("shouldKillAllOnExit: failed to unmarshal bundle json") if spec.Linux != nil {
return true for _, ns := range spec.Linux.Namespaces {
}
if bundleSpec.Linux != nil {
for _, ns := range bundleSpec.Linux.Namespaces {
if ns.Type == specs.PIDNamespace && ns.Path == "" { if ns.Type == specs.PIDNamespace && ns.Path == "" {
return false return false
} }