diff --git a/cmd/server/main.go b/cmd/server/main.go index 3c11eff2..526f151f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -20,46 +20,94 @@ const ( pausePath = "/usr/libexec/ocid/pause" ) +// DefaultConfig returns the default configuration for ocid. +func DefaultConfig() *server.Config { + return &server.Config{ + RootConfig: server.RootConfig{ + Root: ocidRoot, + SandboxDir: filepath.Join(ocidRoot, "sandboxes"), + ContainerDir: filepath.Join(ocidRoot, "containers"), + }, + APIConfig: server.APIConfig{ + Listen: "/var/run/ocid.sock", + }, + RuntimeConfig: server.RuntimeConfig{ + Runtime: "/usr/bin/runc", + Conmon: conmonPath, + SELinux: selinux.SelinuxEnabled(), + }, + ImageConfig: server.ImageConfig{ + Pause: pausePath, + ImageStore: filepath.Join(ocidRoot, "store"), + }, + } +} + +func mergeConfig(config *server.Config, ctx *cli.Context) error { + // Override options set with the CLI. + if ctx.GlobalIsSet("conmon") { + config.Conmon = ctx.GlobalString("conmon") + } + if ctx.GlobalIsSet("pause") { + config.Pause = ctx.GlobalString("pause") + } + if ctx.GlobalIsSet("root") { + config.Root = ctx.GlobalString("root") + } + if ctx.GlobalIsSet("sandboxdir") { + config.SandboxDir = ctx.GlobalString("sandboxdir") + } + if ctx.GlobalIsSet("containerdir") { + config.ContainerDir = ctx.GlobalString("containerdir") + } + if ctx.GlobalIsSet("listen") { + config.Listen = ctx.GlobalString("listen") + } + if ctx.GlobalIsSet("runtime") { + config.Runtime = ctx.GlobalString("runtime") + } + if ctx.GlobalIsSet("selinux") { + config.SELinux = ctx.GlobalBool("selinux") + } + return nil +} + func main() { app := cli.NewApp() app.Name = "ocid" app.Usage = "ocid server" app.Version = "0.0.1" + app.Metadata = map[string]interface{}{ + "config": DefaultConfig(), + } app.Flags = []cli.Flag{ cli.StringFlag{ Name: "conmon", - Value: conmonPath, Usage: "path to the conmon executable", }, cli.StringFlag{ Name: "pause", - Value: pausePath, Usage: "path to the pause executable", }, cli.StringFlag{ Name: "root", - Value: ocidRoot, Usage: "ocid root dir", }, cli.StringFlag{ Name: "sandboxdir", - Value: filepath.Join(ocidRoot, "sandboxes"), Usage: "ocid pod sandbox dir", }, cli.StringFlag{ Name: "containerdir", - Value: filepath.Join(ocidRoot, "containers"), Usage: "ocid container dir", }, cli.StringFlag{ Name: "listen", - Value: "/var/run/ocid.sock", Usage: "path to ocid socket", }, cli.StringFlag{ Name: "runtime", - Value: "/usr/bin/runc", Usage: "OCI runtime path", }, cli.BoolFlag{ @@ -83,12 +131,16 @@ func main() { } app.Before = func(c *cli.Context) error { + // Load the configuration file. + config := c.App.Metadata["config"].(*server.Config) + if err := mergeConfig(config, c); err != nil { + return err + } + if c.GlobalBool("debug") { logrus.SetLevel(logrus.DebugLevel) } - if !c.GlobalBool("selinux") { - selinux.SetDisabled() - } + if path := c.GlobalString("log"); path != "" { f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666) if err != nil { @@ -96,10 +148,7 @@ func main() { } logrus.SetOutput(f) } - if _, err := os.Stat(c.GlobalString("runtime")); os.IsNotExist(err) { - // path to runtime does not exist - return fmt.Errorf("invalid --runtime value %q", err) - } + switch c.GlobalString("log-format") { case "text": // retain logrus's default. @@ -113,25 +162,31 @@ func main() { } app.Action = func(c *cli.Context) error { - socketPath := c.String("listen") + config := c.App.Metadata["config"].(*server.Config) + + if !config.SELinux { + selinux.SetDisabled() + } + + if _, err := os.Stat(config.Runtime); os.IsNotExist(err) { + // path to runtime does not exist + return fmt.Errorf("invalid --runtime value %q", err) + } + // Remove the socket if it already exists - if _, err := os.Stat(socketPath); err == nil { - if err := os.Remove(socketPath); err != nil { + if _, err := os.Stat(config.Listen); err == nil { + if err := os.Remove(config.Listen); err != nil { logrus.Fatal(err) } } - lis, err := net.Listen("unix", socketPath) + lis, err := net.Listen("unix", config.Listen) if err != nil { logrus.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() - containerDir := c.String("containerdir") - sandboxDir := c.String("sandboxdir") - conmonPath := c.String("conmon") - pausePath := c.String("pause") - service, err := server.New(c.String("runtime"), c.String("root"), sandboxDir, containerDir, conmonPath, pausePath) + service, err := server.New(config) if err != nil { logrus.Fatal(err) } diff --git a/server/config.go b/server/config.go new file mode 100644 index 00000000..dad434c0 --- /dev/null +++ b/server/config.go @@ -0,0 +1,135 @@ +package server + +import ( + "bytes" + "io/ioutil" + + "github.com/BurntSushi/toml" +) + +// Config represents the entire set of configuration values that can be set for +// the server. This is intended to be loaded from a toml-encoded config file. +type Config struct { + RootConfig + APIConfig + RuntimeConfig + ImageConfig +} + +// This structure is necessary to fake the TOML tables when parsing, +// while also not requiring a bunch of layered structs for no good +// reason. + +// RootConfig represents the root of the "ocid" TOML config table. +type RootConfig struct { + // Root is a path to the "root directory" where all information not + // explicitly handled by other options will be stored. + Root string `toml:"root"` + + // SandboxDir is the directory where ocid will store all of its sandbox + // state and other information. + SandboxDir string `toml:"sandbox_dir"` + + // ContainerDir is the directory where ocid will store all of its container + // state and other information. + ContainerDir string `toml:"container_dir"` +} + +// APIConfig represents the "ocid.api" TOML config table. +type APIConfig struct { + // Listen is the path to the AF_LOCAL socket on which cri-o will listen. + // This may support proto://addr formats later, but currently this is just + // a path. + Listen string `toml:"listen"` +} + +// RuntimeConfig represents the "ocid.runtime" TOML config table. +type RuntimeConfig struct { + // Runtime is a path to the OCI runtime which ocid will be using. Currently + // the only known working choice is runC, simply because the OCI has not + // yet merged a CLI API (so we assume runC's API here). + Runtime string `toml:"runtime"` + + // Conmon is the path to conmon binary, used for managing the runtime. + Conmon string `toml:"conmon"` + + // SELinux determines whether or not SELinux is used for pod separation. + SELinux bool `toml:"selinux"` +} + +// ImageConfig represents the "ocid.image" TOML config table. +type ImageConfig struct { + // Pause is the path to the statically linked pause container binary, used + // as the entrypoint for infra containers. + // + // TODO(cyphar): This should be replaced with a path to an OCI image + // bundle, once the OCI image/storage code has been implemented. + Pause string `toml:"pause"` + + // ImageStore is the directory where the ocid image store will be stored. + ImageStore string +} + +// tomlConfig is another way of looking at a Config, which is +// TOML-friendly (it has all of the explicit tables). It's just used for +// conversions. +type tomlConfig struct { + Ocid struct { + RootConfig + API struct{ APIConfig } `toml:"api"` + Runtime struct{ RuntimeConfig } `toml:"runtime"` + Image struct{ ImageConfig } `toml:"image"` + } `toml:"ocid"` +} + +func (t *tomlConfig) toConfig(c *Config) { + c.RootConfig = t.Ocid.RootConfig + c.APIConfig = t.Ocid.API.APIConfig + c.RuntimeConfig = t.Ocid.Runtime.RuntimeConfig + c.ImageConfig = t.Ocid.Image.ImageConfig +} + +func (t *tomlConfig) fromConfig(c *Config) { + t.Ocid.RootConfig = c.RootConfig + t.Ocid.API.APIConfig = c.APIConfig + t.Ocid.Runtime.RuntimeConfig = c.RuntimeConfig + t.Ocid.Image.ImageConfig = c.ImageConfig +} + +// FromFile populates the Config from the TOML-encoded file at the given path. +// Returns errors encountered when reading or parsing the files, or nil +// otherwise. +func (c *Config) FromFile(path string) error { + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + t := new(tomlConfig) + t.fromConfig(c) + + _, err = toml.Decode(string(data), t) + if err != nil { + return err + } + + t.toConfig(c) + return nil +} + +// ToFile outputs the given Config as a TOML-encoded file at the given path. +// Returns errors encountered when generating or writing the file, or nil +// otherwise. +func (c *Config) ToFile(path string) error { + var w bytes.Buffer + e := toml.NewEncoder(&w) + + t := new(tomlConfig) + t.fromConfig(c) + + if err := e.Encode(*t); err != nil { + return err + } + + return ioutil.WriteFile(path, w.Bytes(), 0644) +} diff --git a/server/image.go b/server/image.go index c95060a8..ce052e4c 100644 --- a/server/image.go +++ b/server/image.go @@ -55,10 +55,10 @@ func (s *Server) PullImage(ctx context.Context, req *pb.PullImageRequest) (*pb.P return nil, err } - if err = os.Mkdir(filepath.Join(imageStore, tr.StringWithinTransport()), 0755); err != nil { + if err = os.Mkdir(filepath.Join(s.config.ImageStore, tr.StringWithinTransport()), 0755); err != nil { return nil, err } - dir, err := directory.NewReference(filepath.Join(imageStore, tr.StringWithinTransport())) + dir, err := directory.NewReference(filepath.Join(s.config.ImageStore, tr.StringWithinTransport())) if err != nil { return nil, err } diff --git a/server/sandbox.go b/server/sandbox.go index 303dbbd9..43f2c0d6 100644 --- a/server/sandbox.go +++ b/server/sandbox.go @@ -99,7 +99,7 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest if err != nil { return nil, err } - podSandboxDir := filepath.Join(s.sandboxDir, id) + podSandboxDir := filepath.Join(s.config.SandboxDir, id) if _, err = os.Stat(podSandboxDir); err == nil { return nil, fmt.Errorf("pod sandbox (%s) already exists", podSandboxDir) } @@ -119,7 +119,7 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest // creates a spec Generator with the default spec. g := generate.New() - podInfraRootfs := filepath.Join(s.root, "graph/vfs/pause") + podInfraRootfs := filepath.Join(s.config.Root, "graph/vfs/pause") // setup defaults for the pod sandbox g.SetRootPath(filepath.Join(podInfraRootfs, "rootfs")) g.SetRootReadonly(true) @@ -235,7 +235,7 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest if _, err = os.Stat(podInfraRootfs); err != nil { if os.IsNotExist(err) { // TODO: Replace by rootfs creation API when it is ready - if err = utils.CreateInfraRootfs(podInfraRootfs, s.pausePath); err != nil { + if err = utils.CreateInfraRootfs(podInfraRootfs, s.config.Pause); err != nil { return nil, err } } else { @@ -361,7 +361,7 @@ func (s *Server) RemovePodSandbox(ctx context.Context, req *pb.RemovePodSandboxR } // Remove the files related to the sandbox - podSandboxDir := filepath.Join(s.sandboxDir, sb.id) + podSandboxDir := filepath.Join(s.config.SandboxDir, sb.id) if err := os.RemoveAll(podSandboxDir); err != nil { return nil, fmt.Errorf("failed to remove sandbox %s directory: %v", sb.id, err) } diff --git a/server/server.go b/server/server.go index 19af7b4c..77e37837 100644 --- a/server/server.go +++ b/server/server.go @@ -20,15 +20,12 @@ import ( const ( runtimeAPIVersion = "v1alpha1" - imageStore = "/var/lib/ocid/images" ) // Server implements the RuntimeService and ImageService type Server struct { - root string + config Config runtime *oci.Runtime - sandboxDir string - pausePath string stateLock sync.Mutex state *serverState netPlugin ocicni.CNIPlugin @@ -82,7 +79,7 @@ func (s *Server) loadContainer(id string) error { } func (s *Server) loadSandbox(id string) error { - config, err := ioutil.ReadFile(filepath.Join(s.sandboxDir, id, "config.json")) + config, err := ioutil.ReadFile(filepath.Join(s.config.SandboxDir, id, "config.json")) if err != nil { return err } @@ -113,7 +110,7 @@ func (s *Server) loadSandbox(id string) error { processLabel: processLabel, mountLabel: mountLabel, }) - sandboxPath := filepath.Join(s.sandboxDir, id) + sandboxPath := filepath.Join(s.config.SandboxDir, id) if err := label.ReserveLabel(processLabel); err != nil { return err @@ -141,7 +138,7 @@ func (s *Server) loadSandbox(id string) error { } func (s *Server) restore() { - sandboxDir, err := ioutil.ReadDir(s.sandboxDir) + sandboxDir, err := ioutil.ReadDir(s.config.SandboxDir) if err != nil && !os.IsNotExist(err) { logrus.Warnf("could not read sandbox directory %s: %v", sandboxDir, err) } @@ -207,7 +204,7 @@ func (s *Server) releaseContainerName(name string) { } // New creates a new Server with options provided -func New(runtimePath, root, sandboxDir, containerDir, conmonPath, pausePath string) (*Server, error) { +func New(config *Config) (*Server, error) { // TODO: This will go away later when we have wrapper process or systemd acting as // subreaper. if err := utils.SetSubreaper(1); err != nil { @@ -216,15 +213,15 @@ func New(runtimePath, root, sandboxDir, containerDir, conmonPath, pausePath stri utils.StartReaper() - if err := os.MkdirAll(imageStore, 0755); err != nil { + if err := os.MkdirAll(config.ImageStore, 0755); err != nil { return nil, err } - if err := os.MkdirAll(sandboxDir, 0755); err != nil { + if err := os.MkdirAll(config.SandboxDir, 0755); err != nil { return nil, err } - r, err := oci.New(runtimePath, containerDir, conmonPath) + r, err := oci.New(config.Runtime, config.ContainerDir, config.Conmon) if err != nil { return nil, err } @@ -235,11 +232,9 @@ func New(runtimePath, root, sandboxDir, containerDir, conmonPath, pausePath stri return nil, err } s := &Server{ - root: root, - runtime: r, - netPlugin: netPlugin, - sandboxDir: sandboxDir, - pausePath: pausePath, + runtime: r, + netPlugin: netPlugin, + config: *config, state: &serverState{ sandboxes: sandboxes, containers: containers,