diff options
64 files changed, 1787 insertions, 147 deletions
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 3681804ea..3e9772576 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -534,10 +534,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string case "label": // TODO selinux opts and label opts are the same thing s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) + s.Annotations[define.InspectAnnotationLabel] = con[1] case "apparmor": s.ContainerSecurityConfig.ApparmorProfile = con[1] + s.Annotations[define.InspectAnnotationApparmor] = con[1] case "seccomp": s.SeccompProfilePath = con[1] + s.Annotations[define.InspectAnnotationSeccomp] = con[1] default: return fmt.Errorf("invalid --security-opt 2: %q", opt) } diff --git a/cmd/podman/common/types.go b/cmd/podman/common/types.go deleted file mode 100644 index 2427ae975..000000000 --- a/cmd/podman/common/types.go +++ /dev/null @@ -1,3 +0,0 @@ -package common - -var DefaultKernelNamespaces = "cgroup,ipc,net,uts" diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index 2e3386aa9..ec0ddf838 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -11,6 +11,7 @@ import ( "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -28,23 +29,50 @@ var ( podman port ctrID 80/tcp podman port --latest 80`, } + + containerPortCommand = &cobra.Command{ + Use: "port [flags] CONTAINER [PORT]", + Short: portCommand.Short, + Long: portDescription, + RunE: portCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + }, + Example: `podman container port --all + podman container port --latest 80`, + } ) var ( portOpts entities.ContainerPortOptions ) +func portFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers") + flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, Command: portCommand, }) + flags := portCommand.Flags() - flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers") - flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + portFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerPortCommand, + Parent: containerCmd, + }) + + containerPortflags := containerPortCommand.Flags() + portFlags(containerPortflags) + } func port(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go index b112e666a..7803c0c78 100644 --- a/cmd/podman/generate/generate.go +++ b/cmd/podman/generate/generate.go @@ -22,7 +22,7 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: generateCmd, }) } diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go new file mode 100644 index 000000000..86a9cc686 --- /dev/null +++ b/cmd/podman/generate/kube.go @@ -0,0 +1,68 @@ +package pods + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + kubeOptions = entities.GenerateKubeOptions{} + kubeFile = "" + kubeDescription = `Command generates Kubernetes pod and service YAML (v1 specification) from a Podman container or pod. + +Whether the input is for a container or pod, Podman will always generate the specification as a pod.` + + kubeCmd = &cobra.Command{ + Use: "kube [flags] CONTAINER | POD", + Short: "Generate Kubernetes YAML from a container or pod.", + Long: kubeDescription, + RunE: kube, + Args: cobra.ExactArgs(1), + Example: `podman generate kube ctrID + podman generate kube podID + podman generate kube --service podID`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: kubeCmd, + Parent: generateCmd, + }) + flags := kubeCmd.Flags() + flags.BoolVarP(&kubeOptions.Service, "service", "s", false, "Generate YAML for a Kubernetes service object") + flags.StringVarP(&kubeFile, "filename", "f", "", "Write output to the specified path") + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func kube(cmd *cobra.Command, args []string) error { + report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args[0], kubeOptions) + if err != nil { + return err + } + + content, err := ioutil.ReadAll(report.Reader) + if err != nil { + return err + } + if cmd.Flags().Changed("filename") { + if _, err := os.Stat(kubeFile); err == nil { + return errors.Errorf("cannot write to %q", kubeFile) + } + if err := ioutil.WriteFile(kubeFile, content, 0644); err != nil { + return errors.Wrapf(err, "cannot write to %q", kubeFile) + } + return nil + } + + fmt.Println(string(content)) + return nil +} diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index 55d770249..20d9748d4 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -29,7 +29,7 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Mode: []entities.EngineMode{entities.ABIMode}, Command: systemdCmd, Parent: generateCmd, }) diff --git a/cmd/podman/images/trust.go b/cmd/podman/images/trust.go new file mode 100644 index 000000000..88a567871 --- /dev/null +++ b/cmd/podman/images/trust.go @@ -0,0 +1,27 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + trustDescription = `Manages which registries you trust as a source of container images based on their location. + The location is determined by the transport and the registry host of the image. Using this container image docker://quay.io/podman/stable as an example, docker is the transport and quay.io is the registry host.` + trustCmd = &cobra.Command{ + Use: "trust", + Short: "Manage container image trust policy", + Long: trustDescription, + RunE: validate.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: trustCmd, + Parent: imageCmd, + }) +} diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go new file mode 100644 index 000000000..5868f5546 --- /dev/null +++ b/cmd/podman/images/trust_set.go @@ -0,0 +1,56 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + setTrustDescription = "Set default trust policy or add a new trust policy for a registry" + setTrustCommand = &cobra.Command{ + Use: "set [flags] REGISTRY", + Short: "Set default trust policy or a new trust policy for a registry", + Long: setTrustDescription, + Example: "", + RunE: setTrust, + Args: cobra.ExactArgs(1), + } +) + +var ( + setOptions entities.SetTrustOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: setTrustCommand, + Parent: trustCmd, + }) + setFlags := setTrustCommand.Flags() + setFlags.StringVar(&setOptions.PolicyPath, "policypath", "", "") + _ = setFlags.MarkHidden("policypath") + setFlags.StringSliceVarP(&setOptions.PubKeysFile, "pubkeysfile", "f", []string{}, `Path of installed public key(s) to trust for TARGET. +Absolute path to keys is added to policy.json. May +used multiple times to define multiple public keys. +File(s) must exist before using this command`) + setFlags.StringVarP(&setOptions.Type, "type", "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") +} + +func setTrust(cmd *cobra.Command, args []string) error { + validTrustTypes := []string{"accept", "insecureAcceptAnything", "reject", "signedBy"} + + valid, err := image.IsValidImageURI(args[0]) + if err != nil || !valid { + return errors.Wrapf(err, "invalid image uri %s", args[0]) + } + + if !util.StringInSlice(setOptions.Type, validTrustTypes) { + return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type) + } + return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions) +} diff --git a/cmd/podman/images/trust_show.go b/cmd/podman/images/trust_show.go new file mode 100644 index 000000000..23ee6c709 --- /dev/null +++ b/cmd/podman/images/trust_show.go @@ -0,0 +1,77 @@ +package images + +import ( + "fmt" + "os" + "text/tabwriter" + "text/template" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + showTrustDescription = "Display trust policy for the system" + showTrustCommand = &cobra.Command{ + Use: "show [flags] [REGISTRY]", + Short: "Display trust policy for the system", + Long: showTrustDescription, + RunE: showTrust, + Example: "", + } +) + +var ( + showTrustOptions entities.ShowTrustOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: showTrustCommand, + Parent: trustCmd, + }) + showFlags := showTrustCommand.Flags() + showFlags.BoolVarP(&showTrustOptions.JSON, "json", "j", false, "Output as json") + showFlags.StringVar(&showTrustOptions.PolicyPath, "policypath", "", "") + showFlags.BoolVar(&showTrustOptions.Raw, "raw", false, "Output raw policy file") + _ = showFlags.MarkHidden("policypath") + showFlags.StringVar(&showTrustOptions.RegistryPath, "registrypath", "", "") + _ = showFlags.MarkHidden("registrypath") + +} + +func showTrust(cmd *cobra.Command, args []string) error { + report, err := registry.ImageEngine().ShowTrust(registry.Context(), args, showTrustOptions) + if err != nil { + return err + } + if showTrustOptions.Raw { + fmt.Println(report.Raw) + return nil + } + if showTrustOptions.JSON { + b, err := json.MarshalIndent(report.Policies, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + } + + row := "{{.RepoName}}\t{{.Type}}\t{{.GPGId}}\t{{.SignatureStore}}\n" + format := "{{range . }}" + row + "{{end}}" + tmpl, err := template.New("listContainers").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + if err := tmpl.Execute(w, report.Policies); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + return nil +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 422dee90b..76ec7bc8e 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -10,6 +10,7 @@ import ( _ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/manifest" _ "github.com/containers/libpod/cmd/podman/networks" + _ "github.com/containers/libpod/cmd/podman/play" _ "github.com/containers/libpod/cmd/podman/pods" "github.com/containers/libpod/cmd/podman/registry" _ "github.com/containers/libpod/cmd/podman/system" diff --git a/cmd/podman/parse/common.go b/cmd/podman/parse/common.go index a5e9b4fc2..13f425b6d 100644 --- a/cmd/podman/parse/common.go +++ b/cmd/podman/parse/common.go @@ -30,13 +30,20 @@ func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool return errors.Errorf("--all and --latest cannot be used together") } + if (argLen > 0) && specifiedAll { + return errors.Errorf("no arguments are needed with --all") + } + if ignoreArgLen { return nil } - if (argLen > 0) && (specifiedAll || specifiedLatest) { - return errors.Errorf("no arguments are needed with --all or --latest") - } else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) { - return errors.Errorf("no arguments are needed with --all, --latest or --cidfile") + + if argLen > 0 { + if specifiedLatest { + return errors.Errorf("no arguments are needed with --latest") + } else if cidfile && (specifiedLatest || specifiedCIDFile) { + return errors.Errorf("no arguments are needed with --latest or --cidfile") + } } if specifiedCIDFile { diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go new file mode 100644 index 000000000..2499b54b9 --- /dev/null +++ b/cmd/podman/play/kube.go @@ -0,0 +1,101 @@ +package pods + +import ( + "fmt" + "os" + + "github.com/containers/common/pkg/auth" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// playKubeOptionsWrapper allows for separating CLI-only fields from API-only +// fields. +type playKubeOptionsWrapper struct { + entities.PlayKubeOptions + + TLSVerifyCLI bool +} + +var ( + // https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ + defaultSeccompRoot = "/var/lib/kubelet/seccomp" + kubeOptions = playKubeOptionsWrapper{} + kubeDescription = `Command reads in a structured file of Kubernetes YAML. + + It creates the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output.` + + kubeCmd = &cobra.Command{ + Use: "kube [flags] KUBEFILE", + Short: "Play a pod based on Kubernetes YAML.", + Long: kubeDescription, + RunE: kube, + Args: cobra.ExactArgs(1), + Example: `podman play kube nginx.yml + podman play kube --creds user:password --seccomp-profile-root /custom/path apache.yml`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: kubeCmd, + Parent: playCmd, + }) + + flags := kubeCmd.Flags() + flags.SetNormalizeFunc(utils.AliasFlags) + flags.StringVar(&kubeOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.StringVar(&kubeOptions.Network, "network", "", "Connect pod to CNI network(s)") + flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + if !registry.IsRemote() { + flags.StringVar(&kubeOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&kubeOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") + } +} + +func kube(cmd *cobra.Command, args []string) error { + // TLS verification in c/image is controlled via a `types.OptionalBool` + // which allows for distinguishing among set-true, set-false, unspecified + // which is important to implement a sane way of dealing with defaults of + // boolean CLI flags. + if cmd.Flags().Changed("tls-verify") { + kubeOptions.SkipTLSVerify = types.NewOptionalBool(!kubeOptions.TLSVerifyCLI) + } + if kubeOptions.Authfile != "" { + if _, err := os.Stat(kubeOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", kubeOptions.Authfile) + } + } + + report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), args[0], kubeOptions.PlayKubeOptions) + if err != nil { + return err + } + + for _, l := range report.Logs { + fmt.Fprintf(os.Stderr, l) + } + + fmt.Printf("Pod:\n%s\n", report.Pod) + switch len(report.Containers) { + case 0: + return nil + case 1: + fmt.Printf("Container:\n") + default: + fmt.Printf("Containers:\n") + } + for _, ctr := range report.Containers { + fmt.Println(ctr) + } + + return nil +} diff --git a/cmd/podman/play/play.go b/cmd/podman/play/play.go new file mode 100644 index 000000000..b151e5f5d --- /dev/null +++ b/cmd/podman/play/play.go @@ -0,0 +1,26 @@ +package pods + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _play_ + playCmd = &cobra.Command{ + Use: "play", + Short: "Play a pod and its containers from a structured file.", + Long: "Play structured data (e.g., Kubernetes pod or service yaml) based on containers and pods.", + TraverseChildren: true, + RunE: validate.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: playCmd, + }) +} diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 85b96d37b..0a2016496 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/errorhandling" + createconfig "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" @@ -57,7 +58,7 @@ func init() { flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") - flags.StringVar(&share, "share", common.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") + flags.StringVar(&share, "share", createconfig.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") } func create(cmd *cobra.Command, args []string) error { diff --git a/docs/source/Tutorials.rst b/docs/source/Tutorials.rst index 0c7e28c3b..85ae59131 100644 --- a/docs/source/Tutorials.rst +++ b/docs/source/Tutorials.rst @@ -1,2 +1,4 @@ Tutorials ========= + +`Podman Tutorials on GitHub <https://github.com/containers/libpod/tree/master/docs/tutorials>`_ @@ -14,7 +14,7 @@ require ( github.com/containers/conmon v2.0.14+incompatible github.com/containers/image/v5 v5.4.3 github.com/containers/psgo v1.5.0 - github.com/containers/storage v1.19.0 + github.com/containers/storage v1.19.1 github.com/coreos/go-systemd/v22 v22.0.0 github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b github.com/cyphar/filepath-securejoin v0.2.2 @@ -86,6 +86,8 @@ github.com/containers/storage v1.18.2 h1:4cgFbrrgr9nR9xCeOmfpyxk1MtXYZGr7XGPJfAV github.com/containers/storage v1.18.2/go.mod h1:WTBMf+a9ZZ/LbmEVeLHH2TX4CikWbO1Bt+/m58ZHVPg= github.com/containers/storage v1.19.0 h1:bVIF5EglbT5PQnqcN7sE6VWqoQzlToqzjXdz+eNubQg= github.com/containers/storage v1.19.0/go.mod h1:9Xc4rrTubn5hmtBfL+PSJH1XlfTQwR4VAG1NDUIpCts= +github.com/containers/storage v1.19.1 h1:YKIzOO12iaD5Ra0PKFS6emcygbHLmwmQOCQRU/19YAQ= +github.com/containers/storage v1.19.1/go.mod h1:KbXjSwKnx17ejOsjFcCXSf78mCgZkQSLPBNTMRc3XrQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38= @@ -259,6 +261,8 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.4 h1:jFzIFaf586tquEB5EhzQG0HwGNSlgAJpG53G6Ss11wc= github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc= +github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/pgzip v1.2.3 h1:Ce2to9wvs/cuJ2b86/CKQoTYr9VHfpanYosZ0UBJqdw= github.com/klauspost/pgzip v1.2.3/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/libpod/container.go b/libpod/container.go index 5cd719ab6..d4a779b13 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -1221,5 +1221,5 @@ func (c *Container) AutoRemove() bool { if spec.Annotations == nil { return false } - return c.Spec().Annotations[InspectAnnotationAutoremove] == InspectResponseTrue + return c.Spec().Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue } diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 276429b11..ae28dde94 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -16,73 +16,6 @@ import ( "github.com/syndtr/gocapability/capability" ) -const ( - // InspectAnnotationCIDFile is used by Inspect to determine if a - // container ID file was created for the container. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationCIDFile = "io.podman.annotations.cid-file" - // InspectAnnotationAutoremove is used by Inspect to determine if a - // container will be automatically removed on exit. - // If an annotation with this key is found in the OCI spec and is one of - // the two supported boolean values (InspectResponseTrue and - // InspectResponseFalse) it will be used in the output of Inspect(). - InspectAnnotationAutoremove = "io.podman.annotations.autoremove" - // InspectAnnotationVolumesFrom is used by Inspect to identify - // containers whose volumes are are being used by this container. - // It is expected to be a comma-separated list of container names and/or - // IDs. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationVolumesFrom = "io.podman.annotations.volumes-from" - // InspectAnnotationPrivileged is used by Inspect to identify containers - // which are privileged (IE, running with elevated privileges). - // It is expected to be a boolean, populated by one of - // InspectResponseTrue or InspectResponseFalse. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationPrivileged = "io.podman.annotations.privileged" - // InspectAnnotationPublishAll is used by Inspect to identify containers - // which have all the ports from their image published. - // It is expected to be a boolean, populated by one of - // InspectResponseTrue or InspectResponseFalse. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationPublishAll = "io.podman.annotations.publish-all" - // InspectAnnotationInit is used by Inspect to identify containers that - // mount an init binary in. - // It is expected to be a boolean, populated by one of - // InspectResponseTrue or InspectResponseFalse. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationInit = "io.podman.annotations.init" - // InspectAnnotationLabel is used by Inspect to identify containers with - // special SELinux-related settings. It is used to populate the output - // of the SecurityOpt setting. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationLabel = "io.podman.annotations.label" - // InspectAnnotationSeccomp is used by Inspect to identify containers - // with special Seccomp-related settings. It is used to populate the - // output of the SecurityOpt setting in Inspect. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationSeccomp = "io.podman.annotations.seccomp" - // InspectAnnotationApparmor is used by Inspect to identify containers - // with special Apparmor-related settings. It is used to populate the - // output of the SecurityOpt setting. - // If an annotation with this key is found in the OCI spec, it will be - // used in the output of Inspect(). - InspectAnnotationApparmor = "io.podman.annotations.apparmor" - - // InspectResponseTrue is a boolean True response for an inspect - // annotation. - InspectResponseTrue = "TRUE" - // InspectResponseFalse is a boolean False response for an inspect - // annotation. - InspectResponseFalse = "FALSE" -) - // inspectLocked inspects a container for low-level information. // The caller must held c.lock. func (c *Container) inspectLocked(size bool) (*define.InspectContainerData, error) { @@ -452,26 +385,26 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named // Annotations if ctrSpec.Annotations != nil { - hostConfig.ContainerIDFile = ctrSpec.Annotations[InspectAnnotationCIDFile] - if ctrSpec.Annotations[InspectAnnotationAutoremove] == InspectResponseTrue { + hostConfig.ContainerIDFile = ctrSpec.Annotations[define.InspectAnnotationCIDFile] + if ctrSpec.Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue { hostConfig.AutoRemove = true } - if ctrs, ok := ctrSpec.Annotations[InspectAnnotationVolumesFrom]; ok { + if ctrs, ok := ctrSpec.Annotations[define.InspectAnnotationVolumesFrom]; ok { hostConfig.VolumesFrom = strings.Split(ctrs, ",") } - if ctrSpec.Annotations[InspectAnnotationPrivileged] == InspectResponseTrue { + if ctrSpec.Annotations[define.InspectAnnotationPrivileged] == define.InspectResponseTrue { hostConfig.Privileged = true } - if ctrSpec.Annotations[InspectAnnotationInit] == InspectResponseTrue { + if ctrSpec.Annotations[define.InspectAnnotationInit] == define.InspectResponseTrue { hostConfig.Init = true } - if label, ok := ctrSpec.Annotations[InspectAnnotationLabel]; ok { + if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok { hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("label=%s", label)) } - if seccomp, ok := ctrSpec.Annotations[InspectAnnotationSeccomp]; ok { + if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok { hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp)) } - if apparmor, ok := ctrSpec.Annotations[InspectAnnotationApparmor]; ok { + if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok { hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor)) } } diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go new file mode 100644 index 000000000..f6b1c06ea --- /dev/null +++ b/libpod/define/annotations.go @@ -0,0 +1,68 @@ +package define + +const ( + // InspectAnnotationCIDFile is used by Inspect to determine if a + // container ID file was created for the container. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationCIDFile = "io.podman.annotations.cid-file" + // InspectAnnotationAutoremove is used by Inspect to determine if a + // container will be automatically removed on exit. + // If an annotation with this key is found in the OCI spec and is one of + // the two supported boolean values (InspectResponseTrue and + // InspectResponseFalse) it will be used in the output of Inspect(). + InspectAnnotationAutoremove = "io.podman.annotations.autoremove" + // InspectAnnotationVolumesFrom is used by Inspect to identify + // containers whose volumes are are being used by this container. + // It is expected to be a comma-separated list of container names and/or + // IDs. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationVolumesFrom = "io.podman.annotations.volumes-from" + // InspectAnnotationPrivileged is used by Inspect to identify containers + // which are privileged (IE, running with elevated privileges). + // It is expected to be a boolean, populated by one of + // InspectResponseTrue or InspectResponseFalse. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationPrivileged = "io.podman.annotations.privileged" + // InspectAnnotationPublishAll is used by Inspect to identify containers + // which have all the ports from their image published. + // It is expected to be a boolean, populated by one of + // InspectResponseTrue or InspectResponseFalse. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationPublishAll = "io.podman.annotations.publish-all" + // InspectAnnotationInit is used by Inspect to identify containers that + // mount an init binary in. + // It is expected to be a boolean, populated by one of + // InspectResponseTrue or InspectResponseFalse. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationInit = "io.podman.annotations.init" + // InspectAnnotationLabel is used by Inspect to identify containers with + // special SELinux-related settings. It is used to populate the output + // of the SecurityOpt setting. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationLabel = "io.podman.annotations.label" + // InspectAnnotationSeccomp is used by Inspect to identify containers + // with special Seccomp-related settings. It is used to populate the + // output of the SecurityOpt setting in Inspect. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationSeccomp = "io.podman.annotations.seccomp" + // InspectAnnotationApparmor is used by Inspect to identify containers + // with special Apparmor-related settings. It is used to populate the + // output of the SecurityOpt setting. + // If an annotation with this key is found in the OCI spec, it will be + // used in the output of Inspect(). + InspectAnnotationApparmor = "io.podman.annotations.apparmor" + + // InspectResponseTrue is a boolean True response for an inspect + // annotation. + InspectResponseTrue = "TRUE" + // InspectResponseFalse is a boolean False response for an inspect + // annotation. + InspectResponseFalse = "FALSE" +) diff --git a/libpod/info.go b/libpod/info.go index d7ed5bb16..4007e0ce7 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -198,9 +198,15 @@ func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) { if err != nil { return cs, err } + cs.Number = len(cons) for _, con := range cons { state, err := con.State() if err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + // container was probably removed + cs.Number-- + continue + } return cs, err } switch state { @@ -212,7 +218,6 @@ func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) { stopped += 1 } } - cs.Number = len(cons) cs.Paused = paused cs.Stopped = stopped cs.Running = running diff --git a/libpod/kube.go b/libpod/kube.go index 5511d303d..a3c5e912f 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -469,7 +469,7 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) { } var selinuxOpts v1.SELinuxOptions - opts := strings.SplitN(c.config.Spec.Annotations[InspectAnnotationLabel], ":", 2) + opts := strings.SplitN(c.config.Spec.Annotations[define.InspectAnnotationLabel], ":", 2) if len(opts) == 2 { switch opts[0] { case "type": diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go new file mode 100644 index 000000000..23320d346 --- /dev/null +++ b/pkg/api/handlers/libpod/generate.go @@ -0,0 +1,38 @@ +package libpod + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func GenerateKube(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Service bool `schema:"service"` + }{ + // Defaults would go here. + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + + containerEngine := abi.ContainerEngine{Libpod: runtime} + options := entities.GenerateKubeOptions{Service: query.Service} + report, err := containerEngine.GenerateKube(r.Context(), utils.GetName(r), options) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating YAML")) + return + } + + utils.WriteResponse(w, http.StatusOK, report.Reader) +} diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go new file mode 100644 index 000000000..26e02bf4f --- /dev/null +++ b/pkg/api/handlers/libpod/play.go @@ -0,0 +1,64 @@ +package libpod + +import ( + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func PlayKube(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Network string `schema:"reference"` + TLSVerify bool `schema:"tlsVerify"` + }{ + TLSVerify: true, + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + + // Fetch the K8s YAML file from the body, and copy it to a temp file. + tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml") + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + defer os.Remove(tmpfile.Name()) + if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF { + tmpfile.Close() + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) + return + } + if err := tmpfile.Close(); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file")) + return + } + + containerEngine := abi.ContainerEngine{Libpod: runtime} + options := entities.PlayKubeOptions{Network: query.Network, Quiet: true} + if _, found := r.URL.Query()["tlsVerify"]; found { + options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + + report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file")) + return + } + + utils.WriteResponse(w, http.StatusOK, report) +} diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go index 0aceaf5f6..5d125417b 100644 --- a/pkg/api/handlers/swagger/swagger.go +++ b/pkg/api/handlers/swagger/swagger.go @@ -56,6 +56,13 @@ type swagLibpodImagesRemoveResponse struct { Body handlers.LibpodImagesRemoveReport } +// PlayKube response +// swagger:response DocsLibpodPlayKubeResponse +type swagLibpodPlayKubeResponse struct { + // in:body + Body entities.PlayKubeReport +} + // Delete response // swagger:response DocsImageDeleteResponse type swagImageDeleteResponse struct { diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go new file mode 100644 index 000000000..391e60111 --- /dev/null +++ b/pkg/api/server/register_generate.go @@ -0,0 +1,41 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { + // swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube + // --- + // tags: + // - containers + // - pods + // summary: Play a Kubernetes YAML file. + // description: Create and run pods based on a Kubernetes YAML file (pod or service kind). + // parameters: + // - in: path + // name: name:.* + // type: string + // required: true + // description: Name or ID of the container or pod. + // - in: query + // name: service + // type: boolean + // default: false + // description: Generate YAML for a Kubernetes service object. + // produces: + // - application/json + // responses: + // 200: + // description: no error + // schema: + // type: string + // format: binary + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet) + return nil +} diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go new file mode 100644 index 000000000..d04879c19 --- /dev/null +++ b/pkg/api/server/register_play.go @@ -0,0 +1,42 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerPlayHandlers(r *mux.Router) error { + // swagger:operation POST /libpod/play/kube libpod libpodPlayKube + // --- + // tags: + // - containers + // - pods + // summary: Play a Kubernetes YAML file. + // description: Create and run pods based on a Kubernetes YAML file (pod or service kind). + // parameters: + // - in: query + // name: network + // type: string + // description: Connect the pod to this network. + // - in: query + // name: tlsVerify + // type: boolean + // default: true + // description: Require HTTPS and verify signatures when contating registries. + // - in: body + // name: request + // description: Kubernetes YAML file. + // schema: + // type: string + // produces: + // - application/json + // responses: + // 200: + // $ref: "#/responses/DocsLibpodPlayKubeResponse" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKube)).Methods(http.MethodPost) + return nil +} diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index ce2d152e0..a6c5d8e1e 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -98,12 +98,14 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li server.registerDistributionHandlers, server.registerEventsHandlers, server.registerExecHandlers, + server.registerGenerateHandlers, server.registerHealthCheckHandlers, server.registerImagesHandlers, server.registerInfoHandlers, server.registerManifestHandlers, server.registerMonitorHandlers, server.registerPingHandlers, + server.registerPlayHandlers, server.registerPluginsHandlers, server.registerPodsHandlers, server.RegisterSwaggerHandlers, diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go index 2916754b8..d3177133f 100644 --- a/pkg/bindings/generate/generate.go +++ b/pkg/bindings/generate/generate.go @@ -1,4 +1,32 @@ package generate -func GenerateKube() {} -func GenerateSystemd() {} +import ( + "context" + "net/http" + "net/url" + "strconv" + + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" +) + +func GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + params.Set("service", strconv.FormatBool(options.Service)) + + response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nameOrID) + if err != nil { + return nil, err + } + + if response.StatusCode == http.StatusOK { + return &entities.GenerateKubeReport{Reader: response.Body}, nil + } + + // Unpack the error. + return nil, response.Process(nil) +} diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go index a6f03cad2..653558a3c 100644 --- a/pkg/bindings/play/play.go +++ b/pkg/bindings/play/play.go @@ -1,7 +1,43 @@ package play -import "github.com/containers/libpod/pkg/bindings" +import ( + "context" + "net/http" + "net/url" + "os" + "strconv" -func PlayKube() error { - return bindings.ErrNotImplemented + "github.com/containers/image/v5/types" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" +) + +func PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { + var report entities.PlayKubeReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + params := url.Values{} + params.Set("network", options.Network) + if options.SkipTLSVerify != types.OptionalBoolUndefined { + params.Set("tlsVerify", strconv.FormatBool(options.SkipTLSVerify == types.OptionalBoolTrue)) + } + + response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params) + if err != nil { + return nil, err + } + if err := response.Process(&report); err != nil { + return nil, err + } + + return &report, nil } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 2e4e486b5..1bfac4514 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -43,6 +43,7 @@ type ContainerEngine interface { ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) Events(ctx context.Context, opts EventsOptions) error GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error) + GenerateKube(ctx context.Context, nameOrID string, opts GenerateKubeOptions) (*GenerateKubeReport, error) SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error) HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) Info(ctx context.Context) (*define.Info, error) @@ -50,6 +51,7 @@ type ContainerEngine interface { NetworkInspect(ctx context.Context, namesOrIds []string, options NetworkInspectOptions) ([]NetworkInspectReport, error) NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error) NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error) + PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index cb822a70d..ffa71abd6 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -22,6 +22,8 @@ type ImageEngine interface { Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, []error) Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error) + SetTrust(ctx context.Context, args []string, options SetTrustOptions) error + ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error) Shutdown(ctx context.Context) Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error Tree(ctx context.Context, nameOrId string, options ImageTreeOptions) (*ImageTreeReport, error) diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index 6d65b52f8..edd217615 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -1,5 +1,7 @@ package entities +import "io" + // GenerateSystemdOptions control the generation of systemd unit files. type GenerateSystemdOptions struct { // Files - generate files instead of printing to stdout. @@ -20,3 +22,15 @@ type GenerateSystemdReport struct { // entire content. Output string } + +// GenerateKubeOptions control the generation of Kubernetes YAML files. +type GenerateKubeOptions struct { + // Service - generate YAML for a Kubernetes _service_ object. + Service bool +} + +// GenerateKubeReport +type GenerateKubeReport struct { + // Reader - the io.Reader to reader the generated YAML file. + Reader io.Reader +} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 74f27e25f..e116a90b9 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -7,6 +7,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/libpod/pkg/inspect" + "github.com/containers/libpod/pkg/trust" docker "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/opencontainers/go-digest" @@ -285,3 +286,26 @@ type ImageTreeOptions struct { type ImageTreeReport struct { Tree string // TODO: Refactor move presentation work out of server } + +// ShowTrustOptions are the cli options for showing trust +type ShowTrustOptions struct { + JSON bool + PolicyPath string + Raw bool + RegistryPath string +} + +// ShowTrustReport describes the results of show trust +type ShowTrustReport struct { + Raw []byte + SystemRegistriesDirPath string + JSONOutput []byte + Policies []*trust.TrustPolicy +} + +// SetTrustOptions describes the CLI options for setting trust +type SetTrustOptions struct { + PolicyPath string + PubKeysFile []string + Type string +} diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go new file mode 100644 index 000000000..93864c23b --- /dev/null +++ b/pkg/domain/entities/play.go @@ -0,0 +1,36 @@ +package entities + +import "github.com/containers/image/v5/types" + +// PlayKubeOptions controls playing kube YAML files. +type PlayKubeOptions struct { + // Authfile - path to an authentication file. + Authfile string + // CertDir - to a directory containing TLS certifications and keys. + CertDir string + // Credentials - `username:password` for authentication against a + // container registry. + Credentials string + // Network - name of the CNI network to connect to. + Network string + // Quiet - suppress output when pulling images. + Quiet bool + // SignaturePolicy - path to a signature-policy file. + SignaturePolicy string + // SkipTLSVerify - skip https and certificate validation when + // contacting container registries. + SkipTLSVerify types.OptionalBool + // SeccompProfileRoot - path to a directory containing seccomp + // profiles. + SeccompProfileRoot string +} + +// PlayKubeReport contains the results of running play kube. +type PlayKubeReport struct { + // Pod - the ID of the created pod. + Pod string + // Containers - the IDs of the containers running in the created pod. + Containers []string + // Logs - non-fatal erros and log messages while processing. + Logs []string +} diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go index f69ba560e..be5d452bd 100644 --- a/pkg/domain/infra/abi/generate.go +++ b/pkg/domain/infra/abi/generate.go @@ -1,14 +1,18 @@ package abi import ( + "bytes" "context" "fmt" "strings" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/systemd/generate" + "github.com/ghodss/yaml" "github.com/pkg/errors" + k8sAPI "k8s.io/api/core/v1" ) func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { @@ -172,3 +176,84 @@ func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entitie } return ctrName, fmt.Sprintf("%s-%s", kind, name) } + +func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { + var ( + pod *libpod.Pod + podYAML *k8sAPI.Pod + err error + ctr *libpod.Container + servicePorts []k8sAPI.ServicePort + serviceYAML k8sAPI.Service + ) + // Get the container in question. + ctr, err = ic.Libpod.LookupContainer(nameOrID) + if err != nil { + pod, err = ic.Libpod.LookupPod(nameOrID) + if err != nil { + return nil, err + } + podYAML, servicePorts, err = pod.GenerateForKube() + } else { + if len(ctr.Dependencies()) > 0 { + return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies") + } + podYAML, err = ctr.GenerateForKube() + } + if err != nil { + return nil, err + } + + if options.Service { + serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts) + } + + content, err := generateKubeOutput(podYAML, &serviceYAML) + if err != nil { + return nil, err + } + + return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil +} + +func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service) ([]byte, error) { + var ( + output []byte + marshalledPod []byte + marshalledService []byte + err error + ) + + marshalledPod, err = yaml.Marshal(podYAML) + if err != nil { + return nil, err + } + + if serviceYAML != nil { + marshalledService, err = yaml.Marshal(serviceYAML) + if err != nil { + return nil, err + } + } + + header := `# Generation of Kubernetes YAML is still under development! +# +# Save the output of this file and use kubectl create -f to import +# it into Kubernetes. +# +# Created with podman-%s +` + podmanVersion, err := define.GetVersion() + if err != nil { + return nil, err + } + + output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...) + output = append(output, marshalledPod...) + if serviceYAML != nil { + output = append(output, []byte("---\n")...) + output = append(output, marshalledService...) + } + + return output, nil +} diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go new file mode 100644 index 000000000..cd7eec7e6 --- /dev/null +++ b/pkg/domain/infra/abi/play.go @@ -0,0 +1,544 @@ +package abi + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + ann "github.com/containers/libpod/pkg/annotations" + "github.com/containers/libpod/pkg/domain/entities" + envLib "github.com/containers/libpod/pkg/env" + ns "github.com/containers/libpod/pkg/namespaces" + createconfig "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/specgen/generate" + "github.com/containers/libpod/pkg/util" + "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/distribution/reference" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" +) + +const ( + // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath + kubeDirectoryPermission = 0755 + // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath + kubeFilePermission = 0644 +) + +func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { + var ( + containers []*libpod.Container + pod *libpod.Pod + podOptions []libpod.PodCreateOption + podYAML v1.Pod + registryCreds *types.DockerAuthConfig + writer io.Writer + report entities.PlayKubeReport + ) + + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + if err := yaml.Unmarshal(content, &podYAML); err != nil { + return nil, errors.Wrapf(err, "unable to read %q as YAML", path) + } + + if podYAML.Kind != "Pod" { + return nil, errors.Errorf("invalid YAML kind: %q. Pod is the only supported Kubernetes YAML kind", podYAML.Kind) + } + + // check for name collision between pod and container + podName := podYAML.ObjectMeta.Name + if podName == "" { + return nil, errors.Errorf("pod does not have a name") + } + for _, n := range podYAML.Spec.Containers { + if n.Name == podName { + report.Logs = append(report.Logs, + fmt.Sprintf("a container exists with the same name (%q) as the pod in your YAML file; changing pod name to %s_pod\n", podName, podName)) + podName = fmt.Sprintf("%s_pod", podName) + } + } + + podOptions = append(podOptions, libpod.WithInfraContainer()) + podOptions = append(podOptions, libpod.WithPodName(podName)) + // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml + + hostname := podYAML.Spec.Hostname + if hostname == "" { + hostname = podName + } + podOptions = append(podOptions, libpod.WithPodHostname(hostname)) + + if podYAML.Spec.HostNetwork { + podOptions = append(podOptions, libpod.WithPodHostNetwork()) + } + + nsOptions, err := generate.GetNamespaceOptions(strings.Split(createconfig.DefaultKernelNamespaces, ",")) + if err != nil { + return nil, err + } + podOptions = append(podOptions, nsOptions...) + podPorts := getPodPorts(podYAML.Spec.Containers) + podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts)) + + if options.Network != "" { + switch strings.ToLower(options.Network) { + case "bridge", "host": + return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML") + case "": + return nil, errors.Errorf("invalid value passed to --network: must provide a comma-separated list of CNI networks") + default: + // We'll assume this is a comma-separated list of CNI + // networks. + networks := strings.Split(options.Network, ",") + logrus.Debugf("Pod joining CNI networks: %v", networks) + podOptions = append(podOptions, libpod.WithPodNetworks(networks)) + } + } + + // Create the Pod + pod, err = ic.Libpod.NewPod(ctx, podOptions...) + if err != nil { + return nil, err + } + + podInfraID, err := pod.InfraContainerID() + if err != nil { + return nil, err + } + hasUserns := false + if podInfraID != "" { + podCtr, err := ic.Libpod.GetContainer(podInfraID) + if err != nil { + return nil, err + } + mappings, err := podCtr.IDMappings() + if err != nil { + return nil, err + } + hasUserns = len(mappings.UIDMap) > 0 + } + + namespaces := map[string]string{ + // Disabled during code review per mheon + //"pid": fmt.Sprintf("container:%s", podInfraID), + "net": fmt.Sprintf("container:%s", podInfraID), + "ipc": fmt.Sprintf("container:%s", podInfraID), + "uts": fmt.Sprintf("container:%s", podInfraID), + } + if hasUserns { + namespaces["user"] = fmt.Sprintf("container:%s", podInfraID) + } + if !options.Quiet { + writer = os.Stderr + } + + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + DockerCertPath: options.CertDir, + DockerInsecureSkipTLSVerify: options.SkipTLSVerify, + } + + // map from name to mount point + volumes := make(map[string]string) + for _, volume := range podYAML.Spec.Volumes { + hostPath := volume.VolumeSource.HostPath + if hostPath == nil { + return nil, errors.Errorf("HostPath is currently the only supported VolumeSource") + } + if hostPath.Type != nil { + switch *hostPath.Type { + case v1.HostPathDirectoryOrCreate: + if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { + if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil { + return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path) + } + } + // Label a newly created volume + if err := libpod.LabelVolumePath(hostPath.Path); err != nil { + return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path) + } + case v1.HostPathFileOrCreate: + if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { + f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission) + if err != nil { + return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path) + } + if err := f.Close(); err != nil { + logrus.Warnf("Error in closing newly created HostPath file: %v", err) + } + } + // unconditionally label a newly created volume + if err := libpod.LabelVolumePath(hostPath.Path); err != nil { + return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path) + } + case v1.HostPathDirectory: + case v1.HostPathFile: + case v1.HostPathUnset: + // do nothing here because we will verify the path exists in validateVolumeHostDir + break + default: + return nil, errors.Errorf("Directories are the only supported HostPath type") + } + } + + if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil { + return nil, errors.Wrapf(err, "Error in parsing HostPath in YAML") + } + volumes[volume.Name] = hostPath.Path + } + + seccompPaths, err := initializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot) + if err != nil { + return nil, err + } + + for _, container := range podYAML.Spec.Containers { + pullPolicy := util.PullImageMissing + if len(container.ImagePullPolicy) > 0 { + pullPolicy, err = util.ValidatePullType(string(container.ImagePullPolicy)) + if err != nil { + return nil, err + } + } + named, err := reference.ParseNormalizedNamed(container.Image) + if err != nil { + return nil, err + } + // In kube, if the image is tagged with latest, it should always pull + if tagged, isTagged := named.(reference.NamedTagged); isTagged { + if tagged.Tag() == image.LatestTag { + pullPolicy = util.PullImageAlways + } + } + newImage, err := ic.Libpod.ImageRuntime().New(ctx, container.Image, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullPolicy) + if err != nil { + return nil, err + } + conf, err := kubeContainerToCreateConfig(ctx, container, ic.Libpod, newImage, namespaces, volumes, pod.ID(), podInfraID, seccompPaths) + if err != nil { + return nil, err + } + ctr, err := createconfig.CreateContainerFromCreateConfig(ic.Libpod, conf, ctx, pod) + if err != nil { + return nil, err + } + containers = append(containers, ctr) + } + + // start the containers + for _, ctr := range containers { + if err := ctr.Start(ctx, true); err != nil { + // Making this a hard failure here to avoid a mess + // the other containers are in created status + return nil, err + } + } + + report.Pod = pod.ID() + for _, ctr := range containers { + report.Containers = append(report.Containers, ctr.ID()) + } + + return &report, nil +} + +// getPodPorts converts a slice of kube container descriptions to an +// array of ocicni portmapping descriptions usable in libpod +func getPodPorts(containers []v1.Container) []ocicni.PortMapping { + var infraPorts []ocicni.PortMapping + for _, container := range containers { + for _, p := range container.Ports { + if p.HostPort != 0 && p.ContainerPort == 0 { + p.ContainerPort = p.HostPort + } + if p.Protocol == "" { + p.Protocol = "tcp" + } + portBinding := ocicni.PortMapping{ + HostPort: p.HostPort, + ContainerPort: p.ContainerPort, + Protocol: strings.ToLower(string(p.Protocol)), + } + if p.HostIP != "" { + logrus.Debug("HostIP on port bindings is not supported") + } + // only hostPort is utilized in podman context, all container ports + // are accessible inside the shared network namespace + if p.HostPort != 0 { + infraPorts = append(infraPorts, portBinding) + } + + } + } + return infraPorts +} + +func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfig *createconfig.UserConfig, containerYAML v1.Container) { + if containerYAML.SecurityContext == nil { + return + } + if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil { + securityConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem + } + if containerYAML.SecurityContext.Privileged != nil { + securityConfig.Privileged = *containerYAML.SecurityContext.Privileged + } + + if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil { + securityConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation + } + + if seopt := containerYAML.SecurityContext.SELinuxOptions; seopt != nil { + if seopt.User != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=user:%s", seopt.User)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("user:%s", seopt.User)) + } + if seopt.Role != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=role:%s", seopt.Role)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("role:%s", seopt.Role)) + } + if seopt.Type != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=type:%s", seopt.Type)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("type:%s", seopt.Type)) + } + if seopt.Level != "" { + securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=level:%s", seopt.Level)) + securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("level:%s", seopt.Level)) + } + } + if caps := containerYAML.SecurityContext.Capabilities; caps != nil { + for _, capability := range caps.Add { + securityConfig.CapAdd = append(securityConfig.CapAdd, string(capability)) + } + for _, capability := range caps.Drop { + securityConfig.CapDrop = append(securityConfig.CapDrop, string(capability)) + } + } + if containerYAML.SecurityContext.RunAsUser != nil { + userConfig.User = fmt.Sprintf("%d", *containerYAML.SecurityContext.RunAsUser) + } + if containerYAML.SecurityContext.RunAsGroup != nil { + if userConfig.User == "" { + userConfig.User = "0" + } + userConfig.User = fmt.Sprintf("%s:%d", userConfig.User, *containerYAML.SecurityContext.RunAsGroup) + } +} + +// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container +func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) { + var ( + containerConfig createconfig.CreateConfig + pidConfig createconfig.PidConfig + networkConfig createconfig.NetworkConfig + cgroupConfig createconfig.CgroupConfig + utsConfig createconfig.UtsConfig + ipcConfig createconfig.IpcConfig + userConfig createconfig.UserConfig + securityConfig createconfig.SecurityConfig + ) + + // The default for MemorySwappiness is -1, not 0 + containerConfig.Resources.MemorySwappiness = -1 + + containerConfig.Image = containerYAML.Image + containerConfig.ImageID = newImage.ID() + containerConfig.Name = containerYAML.Name + containerConfig.Tty = containerYAML.TTY + + containerConfig.Pod = podID + + imageData, _ := newImage.Inspect(ctx) + + userConfig.User = "0" + if imageData != nil { + userConfig.User = imageData.Config.User + } + + setupSecurityContext(&securityConfig, &userConfig, containerYAML) + + securityConfig.SeccompProfilePath = seccompPaths.findForContainer(containerConfig.Name) + + containerConfig.Command = []string{} + if imageData != nil && imageData.Config != nil { + containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...) + } + if len(containerYAML.Command) != 0 { + containerConfig.Command = append(containerConfig.Command, containerYAML.Command...) + } else if imageData != nil && imageData.Config != nil { + containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...) + } + if imageData != nil && len(containerConfig.Command) == 0 { + return nil, errors.Errorf("No command specified in container YAML or as CMD or ENTRYPOINT in this image for %s", containerConfig.Name) + } + + containerConfig.UserCommand = containerConfig.Command + + containerConfig.StopSignal = 15 + + containerConfig.WorkDir = "/" + if imageData != nil { + // FIXME, + // we are currently ignoring imageData.Config.ExposedPorts + containerConfig.BuiltinImgVolumes = imageData.Config.Volumes + if imageData.Config.WorkingDir != "" { + containerConfig.WorkDir = imageData.Config.WorkingDir + } + containerConfig.Labels = imageData.Config.Labels + if imageData.Config.StopSignal != "" { + stopSignal, err := util.ParseSignal(imageData.Config.StopSignal) + if err != nil { + return nil, err + } + containerConfig.StopSignal = stopSignal + } + } + + if containerYAML.WorkingDir != "" { + containerConfig.WorkDir = containerYAML.WorkingDir + } + // If the user does not pass in ID mappings, just set to basics + if userConfig.IDMappings == nil { + userConfig.IDMappings = &storage.IDMappingOptions{} + } + + networkConfig.NetMode = ns.NetworkMode(namespaces["net"]) + ipcConfig.IpcMode = ns.IpcMode(namespaces["ipc"]) + utsConfig.UtsMode = ns.UTSMode(namespaces["uts"]) + // disabled in code review per mheon + //containerConfig.PidMode = ns.PidMode(namespaces["pid"]) + userConfig.UsernsMode = ns.UsernsMode(namespaces["user"]) + if len(containerConfig.WorkDir) == 0 { + containerConfig.WorkDir = "/" + } + + containerConfig.Pid = pidConfig + containerConfig.Network = networkConfig + containerConfig.Uts = utsConfig + containerConfig.Ipc = ipcConfig + containerConfig.Cgroup = cgroupConfig + containerConfig.User = userConfig + containerConfig.Security = securityConfig + + annotations := make(map[string]string) + if infraID != "" { + annotations[ann.SandboxID] = infraID + annotations[ann.ContainerType] = ann.ContainerTypeContainer + } + containerConfig.Annotations = annotations + + // Environment Variables + envs := map[string]string{} + if imageData != nil { + imageEnv, err := envLib.ParseSlice(imageData.Config.Env) + if err != nil { + return nil, errors.Wrap(err, "error parsing image environment variables") + } + envs = imageEnv + } + for _, e := range containerYAML.Env { + envs[e.Name] = e.Value + } + containerConfig.Env = envs + + for _, volume := range containerYAML.VolumeMounts { + hostPath, exists := volumes[volume.Name] + if !exists { + return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name) + } + if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil { + return nil, errors.Wrapf(err, "error in parsing MountPath") + } + containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", hostPath, volume.MountPath)) + } + return &containerConfig, nil +} + +// kubeSeccompPaths holds information about a pod YAML's seccomp configuration +// it holds both container and pod seccomp paths +type kubeSeccompPaths struct { + containerPaths map[string]string + podPath string +} + +// findForContainer checks whether a container has a seccomp path configured for it +// if not, it returns the podPath, which should always have a value +func (k *kubeSeccompPaths) findForContainer(ctrName string) string { + if path, ok := k.containerPaths[ctrName]; ok { + return path + } + return k.podPath +} + +// initializeSeccompPaths takes annotations from the pod object metadata and finds annotations pertaining to seccomp +// it parses both pod and container level +// if the annotation is of the form "localhost/%s", the seccomp profile will be set to profileRoot/%s +func initializeSeccompPaths(annotations map[string]string, profileRoot string) (*kubeSeccompPaths, error) { + seccompPaths := &kubeSeccompPaths{containerPaths: make(map[string]string)} + var err error + if annotations != nil { + for annKeyValue, seccomp := range annotations { + // check if it is prefaced with container.seccomp.security.alpha.kubernetes.io/ + prefixAndCtr := strings.Split(annKeyValue, "/") + if prefixAndCtr[0]+"/" != v1.SeccompContainerAnnotationKeyPrefix { + continue + } else if len(prefixAndCtr) != 2 { + // this could be caused by a user inputting either of + // container.seccomp.security.alpha.kubernetes.io{,/} + // both of which are invalid + return nil, errors.Errorf("Invalid seccomp path: %s", prefixAndCtr[0]) + } + + path, err := verifySeccompPath(seccomp, profileRoot) + if err != nil { + return nil, err + } + seccompPaths.containerPaths[prefixAndCtr[1]] = path + } + + podSeccomp, ok := annotations[v1.SeccompPodAnnotationKey] + if ok { + seccompPaths.podPath, err = verifySeccompPath(podSeccomp, profileRoot) + } else { + seccompPaths.podPath, err = libpod.DefaultSeccompPath() + } + if err != nil { + return nil, err + } + } + return seccompPaths, nil +} + +// verifySeccompPath takes a path and checks whether it is a default, unconfined, or a path +// the available options are parsed as defined in https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp +func verifySeccompPath(path string, profileRoot string) (string, error) { + switch path { + case v1.DeprecatedSeccompProfileDockerDefault: + fallthrough + case v1.SeccompProfileRuntimeDefault: + return libpod.DefaultSeccompPath() + case "unconfined": + return path, nil + default: + parts := strings.Split(path, "/") + if parts[0] == "localhost" { + return filepath.Join(profileRoot, parts[1]), nil + } + return "", errors.Errorf("invalid seccomp path: %s", path) + } +} diff --git a/pkg/domain/infra/abi/trust.go b/pkg/domain/infra/abi/trust.go new file mode 100644 index 000000000..5b89c91d9 --- /dev/null +++ b/pkg/domain/infra/abi/trust.go @@ -0,0 +1,171 @@ +package abi + +import ( + "context" + "encoding/json" + "io/ioutil" + "os" + "strings" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/trust" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func (ir *ImageEngine) ShowTrust(ctx context.Context, args []string, options entities.ShowTrustOptions) (*entities.ShowTrustReport, error) { + var ( + err error + report entities.ShowTrustReport + ) + policyPath := trust.DefaultPolicyPath(ir.Libpod.SystemContext()) + if len(options.PolicyPath) > 0 { + policyPath = options.PolicyPath + } + report.Raw, err = ioutil.ReadFile(policyPath) + if err != nil { + return nil, errors.Wrapf(err, "unable to read %s", policyPath) + } + if options.Raw { + return &report, nil + } + report.SystemRegistriesDirPath = trust.RegistriesDirPath(ir.Libpod.SystemContext()) + if len(options.RegistryPath) > 0 { + report.SystemRegistriesDirPath = options.RegistryPath + } + policyContentStruct, err := trust.GetPolicy(policyPath) + if err != nil { + return nil, errors.Wrapf(err, "could not read trust policies") + } + report.Policies, err = getPolicyShowOutput(policyContentStruct, report.SystemRegistriesDirPath) + if err != nil { + return nil, errors.Wrapf(err, "could not show trust policies") + } + return &report, nil +} + +func (ir *ImageEngine) SetTrust(ctx context.Context, args []string, options entities.SetTrustOptions) error { + var ( + policyContentStruct trust.PolicyContent + newReposContent []trust.RepoContent + ) + trustType := options.Type + if trustType == "accept" { + trustType = "insecureAcceptAnything" + } + + pubkeysfile := options.PubKeysFile + if len(pubkeysfile) == 0 && trustType == "signedBy" { + return errors.Errorf("At least one public key must be defined for type 'signedBy'") + } + + policyPath := trust.DefaultPolicyPath(ir.Libpod.SystemContext()) + if len(options.PolicyPath) > 0 { + policyPath = options.PolicyPath + } + _, err := os.Stat(policyPath) + if !os.IsNotExist(err) { + policyContent, err := ioutil.ReadFile(policyPath) + if err != nil { + return errors.Wrapf(err, "unable to read %s", policyPath) + } + if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { + return errors.Errorf("could not read trust policies") + } + } + if len(pubkeysfile) != 0 { + for _, filepath := range pubkeysfile { + newReposContent = append(newReposContent, trust.RepoContent{Type: trustType, KeyType: "GPGKeys", KeyPath: filepath}) + } + } else { + newReposContent = append(newReposContent, trust.RepoContent{Type: trustType}) + } + if args[0] == "default" { + policyContentStruct.Default = newReposContent + } else { + if len(policyContentStruct.Default) == 0 { + return errors.Errorf("Default trust policy must be set.") + } + registryExists := false + for transport, transportval := range policyContentStruct.Transports { + _, registryExists = transportval[args[0]] + if registryExists { + policyContentStruct.Transports[transport][args[0]] = newReposContent + break + } + } + if !registryExists { + if policyContentStruct.Transports == nil { + policyContentStruct.Transports = make(map[string]trust.RepoMap) + } + if policyContentStruct.Transports["docker"] == nil { + policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent) + } + policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...) + } + } + + data, err := json.MarshalIndent(policyContentStruct, "", " ") + if err != nil { + return errors.Wrapf(err, "error setting trust policy") + } + return ioutil.WriteFile(policyPath, data, 0644) +} + +func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]*trust.TrustPolicy, error) { + var output []*trust.TrustPolicy + + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, err + } + + if len(policyContentStruct.Default) > 0 { + defaultPolicyStruct := trust.TrustPolicy{ + Name: "* (default)", + RepoName: "default", + Type: trustTypeDescription(policyContentStruct.Default[0].Type), + } + output = append(output, &defaultPolicyStruct) + } + for _, transval := range policyContentStruct.Transports { + for repo, repoval := range transval { + tempTrustShowOutput := trust.TrustPolicy{ + Name: repo, + RepoName: repo, + Type: repoval[0].Type, + } + // TODO - keyarr is not used and I don't know its intent; commenting out for now for someone to fix later + //keyarr := []string{} + uids := []string{} + for _, repoele := range repoval { + if len(repoele.KeyPath) > 0 { + //keyarr = append(keyarr, repoele.KeyPath) + uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) + } + if len(repoele.KeyData) > 0 { + //keyarr = append(keyarr, string(repoele.KeyData)) + uids = append(uids, trust.GetGPGIdFromKeyData(repoele.KeyData)...) + } + } + tempTrustShowOutput.GPGId = strings.Join(uids, ", ") + + registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) + if registryNamespace != nil { + tempTrustShowOutput.SignatureStore = registryNamespace.SigStore + } + output = append(output, &tempTrustShowOutput) + } + } + return output, nil +} + +var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} + +func trustTypeDescription(trustType string) string { + trustDescription, exist := typeDescription[trustType] + if !exist { + logrus.Warnf("invalid trust type %s", trustType) + } + return trustDescription +} diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index 3cd483053..eb5587f89 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -3,6 +3,7 @@ package tunnel import ( "context" + "github.com/containers/libpod/pkg/bindings/generate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" ) @@ -10,3 +11,7 @@ import ( func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { return nil, errors.New("not implemented for tunnel") } + +func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { + return generate.GenerateKube(ic.ClientCxt, nameOrID, options) +} diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go new file mode 100644 index 000000000..15383a703 --- /dev/null +++ b/pkg/domain/infra/tunnel/play.go @@ -0,0 +1,12 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/bindings/play" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { + return play.PlayKube(ic.ClientCxt, path, options) +} diff --git a/pkg/domain/infra/tunnel/trust.go b/pkg/domain/infra/tunnel/trust.go new file mode 100644 index 000000000..a976bfdc2 --- /dev/null +++ b/pkg/domain/infra/tunnel/trust.go @@ -0,0 +1,16 @@ +package tunnel + +import ( + "context" + "errors" + + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ir *ImageEngine) ShowTrust(ctx context.Context, args []string, options entities.ShowTrustOptions) (*entities.ShowTrustReport, error) { + return nil, errors.New("not implemented") +} + +func (ir *ImageEngine) SetTrust(ctx context.Context, args []string, options entities.SetTrustOptions) error { + return errors.New("not implemented") +} diff --git a/pkg/spec/namespaces.go b/pkg/spec/namespaces.go index aebc90f68..40364b054 100644 --- a/pkg/spec/namespaces.go +++ b/pkg/spec/namespaces.go @@ -17,6 +17,10 @@ import ( "github.com/sirupsen/logrus" ) +// DefaultKernelNamespaces is a comma-separated list of default kernel +// namespaces. +const DefaultKernelNamespaces = "cgroup,ipc,net,uts" + // ToCreateOptions converts the input to a slice of container create options. func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) { var portBindings []ocicni.PortMapping @@ -154,9 +158,9 @@ func (c *NetworkConfig) ConfigureGenerator(g *generate.Generator) error { } if c.PublishAll { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue } else { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse + g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse } return nil diff --git a/pkg/spec/security.go b/pkg/spec/security.go index 0f8d36f00..6d74e97e6 100644 --- a/pkg/spec/security.go +++ b/pkg/spec/security.go @@ -6,6 +6,7 @@ import ( "github.com/containers/common/pkg/capabilities" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/util" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -184,11 +185,11 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon } switch splitOpt[0] { case "label": - configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1] + configSpec.Annotations[define.InspectAnnotationLabel] = splitOpt[1] case "seccomp": - configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1] + configSpec.Annotations[define.InspectAnnotationSeccomp] = splitOpt[1] case "apparmor": - configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1] + configSpec.Annotations[define.InspectAnnotationApparmor] = splitOpt[1] } } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 41ed5f1f0..77e92ae29 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -7,6 +7,7 @@ import ( cconfig "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/sysinfo" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/rootless" @@ -436,29 +437,29 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } if config.CidFile != "" { - configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile + configSpec.Annotations[define.InspectAnnotationCIDFile] = config.CidFile } if config.Rm { - configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue + configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue } else { - configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse + configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse } if len(config.VolumesFrom) > 0 { - configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",") + configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",") } if config.Security.Privileged { - configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue + configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue } else { - configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse + configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse } if config.Init { - configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue + configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseTrue } else { - configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse + configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse } return configSpec, nil diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 96c65b551..138d9e0cd 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -438,9 +438,9 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt g.Config.Annotations = make(map[string]string) } if s.PublishExposedPorts { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue } else { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse + g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse } return nil diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 8136c0993..a2bb66a44 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -6,6 +6,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/specgen" @@ -327,19 +328,19 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt //} if s.Remove { - configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue + configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue } else { - configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse + configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse } if len(s.VolumesFrom) > 0 { - configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",") + configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",") } if s.Privileged { - configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue + configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue } else { - configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse + configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse } // TODO Init might not make it into the specgen and therefore is not available here. We should deal diff --git a/pkg/trust/config.go b/pkg/trust/config.go new file mode 100644 index 000000000..0bafc722b --- /dev/null +++ b/pkg/trust/config.go @@ -0,0 +1,12 @@ +package trust + +// Trust Policy describes a basic trust policy configuration +type TrustPolicy struct { + Name string `json:"name"` + RepoName string `json:"repo_name,omitempty"` + Keys []string `json:"keys,omitempty"` + SignatureStore string `json:"sigstore"` + Transport string `json:"transport"` + Type string `json:"type"` + GPGId string `json:"gpg_id,omitempty"` +} diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index e4f487634..389f2c822 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -21,7 +21,6 @@ var _ = Describe("Podman generate kube", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 16f7af55e..9daf266b8 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -217,7 +217,6 @@ var _ = Describe("Podman generate kube", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index ce31c9ad2..5bb86d558 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -20,7 +20,6 @@ var _ = Describe("Podman port", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/systemd_test.go b/test/e2e/systemd_test.go index c56fb00f2..1275670eb 100644 --- a/test/e2e/systemd_test.go +++ b/test/e2e/systemd_test.go @@ -23,7 +23,6 @@ var _ = Describe("Podman systemd", func() { ) BeforeEach(func() { - Skip(v2fail) SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { @@ -86,6 +85,7 @@ WantedBy=multi-user.target cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() Expect(err).To(BeNil()) if cgroupsv2 { + // TODO: Find a way to enable this for v2 Skip("systemd test does not work in cgroups V2 mode yet") } diff --git a/test/e2e/trust_test.go b/test/e2e/trust_test.go index 2da370194..8c97e6b28 100644 --- a/test/e2e/trust_test.go +++ b/test/e2e/trust_test.go @@ -21,7 +21,6 @@ var _ = Describe("Podman trust", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/vendor/github.com/containers/storage/.cirrus.yml b/vendor/github.com/containers/storage/.cirrus.yml index 3463adf90..a55b5a189 100644 --- a/vendor/github.com/containers/storage/.cirrus.yml +++ b/vendor/github.com/containers/storage/.cirrus.yml @@ -19,9 +19,9 @@ env: #### # GCE project where images live IMAGE_PROJECT: "libpod-218412" - _BUILT_IMAGE_SUFFIX: "libpod-5874660151656448" - FEDORA_CACHE_IMAGE_NAME: "fedora-31-${_BUILT_IMAGE_SUFFIX}" - PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-30-${_BUILT_IMAGE_SUFFIX}" + _BUILT_IMAGE_SUFFIX: "libpod-6301182083727360" + FEDORA_CACHE_IMAGE_NAME: "fedora-32-${_BUILT_IMAGE_SUFFIX}" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-31-${_BUILT_IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "ubuntu-19-${_BUILT_IMAGE_SUFFIX}" PRIOR_UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-${_BUILT_IMAGE_SUFFIX}" diff --git a/vendor/github.com/containers/storage/VERSION b/vendor/github.com/containers/storage/VERSION index 815d5ca06..66e2ae6c2 100644 --- a/vendor/github.com/containers/storage/VERSION +++ b/vendor/github.com/containers/storage/VERSION @@ -1 +1 @@ -1.19.0 +1.19.1 diff --git a/vendor/github.com/containers/storage/containers.go b/vendor/github.com/containers/storage/containers.go index 0c9434a38..96e7c75fc 100644 --- a/vendor/github.com/containers/storage/containers.go +++ b/vendor/github.com/containers/storage/containers.go @@ -148,10 +148,20 @@ func (c *Container) ProcessLabel() string { } func (c *Container) MountOpts() []string { - if mountOpts, ok := c.Flags["MountOpts"].([]string); ok { + switch c.Flags["MountOpts"].(type) { + case []string: + return c.Flags["MountOpts"].([]string) + case []interface{}: + var mountOpts []string + for _, v := range c.Flags["MountOpts"].([]interface{}) { + if flag, ok := v.(string); ok { + mountOpts = append(mountOpts, flag) + } + } return mountOpts + default: + return nil } - return nil } func (r *containerStore) Containers() ([]Container, error) { diff --git a/vendor/github.com/containers/storage/drivers/zfs/zfs.go b/vendor/github.com/containers/storage/drivers/zfs/zfs.go index c9c8c5c3c..3e850d136 100644 --- a/vendor/github.com/containers/storage/drivers/zfs/zfs.go +++ b/vendor/github.com/containers/storage/drivers/zfs/zfs.go @@ -384,9 +384,21 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr } }() + // In the case of a read-only mount we first mount read-write so we can set the + // correct permissions on the mount point and remount read-only afterwards. + remountReadOnly := false mountOptions := d.options.mountOptions if len(options.Options) > 0 { - mountOptions = strings.Join(options.Options, ",") + var newOptions []string + for _, option := range options.Options { + if option == "ro" { + // Filter out read-only mount option but remember for later remounting. + remountReadOnly = true + } else { + newOptions = append(newOptions, option) + } + } + mountOptions = strings.Join(newOptions, ",") } filesystem := d.zfsPath(id) @@ -409,7 +421,14 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr // this could be our first mount after creation of the filesystem, and the root dir may still have root // permissions instead of the remapped root uid:gid (if user namespaces are enabled): if err := os.Chown(mountpoint, rootUID, rootGID); err != nil { - return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err) + return "", errors.Wrapf(err, "modifying zfs mountpoint (%s) ownership", mountpoint) + } + + if remountReadOnly { + opts = label.FormatMountLabel("remount,ro", options.MountLabel) + if err := mount.Mount(filesystem, mountpoint, "zfs", opts); err != nil { + return "", errors.Wrap(err, "error remounting zfs mount read-only") + } } return mountpoint, nil diff --git a/vendor/github.com/containers/storage/go.mod b/vendor/github.com/containers/storage/go.mod index 51c1c1f8a..a7742bcdd 100644 --- a/vendor/github.com/containers/storage/go.mod +++ b/vendor/github.com/containers/storage/go.mod @@ -6,7 +6,7 @@ require ( github.com/Microsoft/hcsshim v0.8.7 github.com/docker/go-units v0.4.0 github.com/hashicorp/go-multierror v1.0.0 - github.com/klauspost/compress v1.10.4 + github.com/klauspost/compress v1.10.5 github.com/klauspost/pgzip v1.2.3 github.com/mattn/go-shellwords v1.0.10 github.com/mistifyio/go-zfs v2.1.1+incompatible diff --git a/vendor/github.com/containers/storage/go.sum b/vendor/github.com/containers/storage/go.sum index a5aa99bc5..97076ffa6 100644 --- a/vendor/github.com/containers/storage/go.sum +++ b/vendor/github.com/containers/storage/go.sum @@ -41,8 +41,8 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.4 h1:jFzIFaf586tquEB5EhzQG0HwGNSlgAJpG53G6Ss11wc= -github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc= +github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/pgzip v1.2.3 h1:Ce2to9wvs/cuJ2b86/CKQoTYr9VHfpanYosZ0UBJqdw= github.com/klauspost/pgzip v1.2.3/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= diff --git a/vendor/github.com/containers/storage/layers.go b/vendor/github.com/containers/storage/layers.go index 17227266e..a8ebf9e1e 100644 --- a/vendor/github.com/containers/storage/layers.go +++ b/vendor/github.com/containers/storage/layers.go @@ -992,6 +992,9 @@ func (r *layerStore) deleteInternal(id string) error { if err == nil { os.Remove(r.tspath(id)) delete(r.byid, id) + for _, name := range layer.Names { + delete(r.byname, name) + } r.idindex.Delete(id) mountLabel := layer.MountLabel if layer.MountPoint != "" { diff --git a/vendor/github.com/containers/storage/pkg/archive/archive.go b/vendor/github.com/containers/storage/pkg/archive/archive.go index d9a2e473c..bf819a801 100644 --- a/vendor/github.com/containers/storage/pkg/archive/archive.go +++ b/vendor/github.com/containers/storage/pkg/archive/archive.go @@ -394,7 +394,7 @@ func fillGo18FileTypeBits(mode int64, fi os.FileInfo) int64 { // to a tar header func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { capability, err := system.Lgetxattr(path, "security.capability") - if err != nil && err != system.EOPNOTSUPP { + if err != nil && err != system.EOPNOTSUPP && err != system.ErrNotSupportedPlatform { return err } if capability != nil { @@ -407,7 +407,7 @@ func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { // ReadUserXattrToTarHeader reads user.* xattr from filesystem to a tar header func ReadUserXattrToTarHeader(path string, hdr *tar.Header) error { xattrs, err := system.Llistxattr(path) - if err != nil && err != system.EOPNOTSUPP { + if err != nil && err != system.EOPNOTSUPP && err != system.ErrNotSupportedPlatform { return err } for _, key := range xattrs { diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go index 697f30b5c..43b84d769 100644 --- a/vendor/github.com/containers/storage/store.go +++ b/vendor/github.com/containers/storage/store.go @@ -3397,7 +3397,7 @@ func copyStringInterfaceMap(m map[string]interface{}) map[string]interface{} { } // defaultConfigFile path to the system wide storage.conf file -const defaultConfigFile = "/etc/containers/storage.conf" +var defaultConfigFile = "/etc/containers/storage.conf" // AutoUserNsMinSize is the minimum size for automatically created user namespaces const AutoUserNsMinSize = 1024 @@ -3409,6 +3409,11 @@ const AutoUserNsMaxSize = 65536 // creating a user namespace. const RootAutoUserNsUser = "containers" +// SetDefaultConfigFilePath sets the default configuration to the specified path +func SetDefaultConfigFilePath(path string) { + defaultConfigFile = path +} + // DefaultConfigFile returns the path to the storage config file used func DefaultConfigFile(rootless bool) (string, error) { if rootless { diff --git a/vendor/github.com/klauspost/compress/zstd/blockdec.go b/vendor/github.com/klauspost/compress/zstd/blockdec.go index 63062ffa6..c2f855e75 100644 --- a/vendor/github.com/klauspost/compress/zstd/blockdec.go +++ b/vendor/github.com/klauspost/compress/zstd/blockdec.go @@ -131,17 +131,25 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error { b.Type = blockType((bh >> 1) & 3) // find size. cSize := int(bh >> 3) + maxSize := maxBlockSize switch b.Type { case blockTypeReserved: return ErrReservedBlockType case blockTypeRLE: b.RLESize = uint32(cSize) + if b.lowMem { + maxSize = cSize + } cSize = 1 case blockTypeCompressed: if debug { println("Data size on stream:", cSize) } b.RLESize = 0 + maxSize = maxCompressedBlockSize + if windowSize < maxCompressedBlockSize && b.lowMem { + maxSize = int(windowSize) + } if cSize > maxCompressedBlockSize || uint64(cSize) > b.WindowSize { if debug { printf("compressed block too big: csize:%d block: %+v\n", uint64(cSize), b) @@ -160,8 +168,8 @@ func (b *blockDec) reset(br byteBuffer, windowSize uint64) error { b.dataStorage = make([]byte, 0, maxBlockSize) } } - if cap(b.dst) <= maxBlockSize { - b.dst = make([]byte, 0, maxBlockSize+1) + if cap(b.dst) <= maxSize { + b.dst = make([]byte, 0, maxSize+1) } var err error b.data, err = br.readBig(cSize, b.dataStorage) @@ -679,8 +687,11 @@ func (b *blockDec) decodeCompressed(hist *history) error { println("initializing sequences:", err) return err } - - err = seqs.decode(nSeqs, br, hist.b) + hbytes := hist.b + if len(hbytes) > hist.windowSize { + hbytes = hbytes[len(hbytes)-hist.windowSize:] + } + err = seqs.decode(nSeqs, br, hbytes) if err != nil { return err } diff --git a/vendor/github.com/klauspost/compress/zstd/framedec.go b/vendor/github.com/klauspost/compress/zstd/framedec.go index e38f34a9b..780880ebe 100644 --- a/vendor/github.com/klauspost/compress/zstd/framedec.go +++ b/vendor/github.com/klauspost/compress/zstd/framedec.go @@ -233,7 +233,11 @@ func (d *frameDec) reset(br byteBuffer) error { return ErrWindowSizeTooSmall } d.history.windowSize = int(d.WindowSize) - d.history.maxSize = d.history.windowSize + maxBlockSize + if d.o.lowMem && d.history.windowSize < maxBlockSize { + d.history.maxSize = d.history.windowSize * 2 + } else { + d.history.maxSize = d.history.windowSize + maxBlockSize + } // history contains input - maybe we do something d.rawInput = br return nil @@ -320,8 +324,8 @@ func (d *frameDec) checkCRC() error { func (d *frameDec) initAsync() { if !d.o.lowMem && !d.SingleSegment { - // set max extra size history to 20MB. - d.history.maxSize = d.history.windowSize + maxBlockSize*10 + // set max extra size history to 10MB. + d.history.maxSize = d.history.windowSize + maxBlockSize*5 } // re-alloc if more than one extra block size. if d.o.lowMem && cap(d.history.b) > d.history.maxSize+maxBlockSize { diff --git a/vendor/modules.txt b/vendor/modules.txt index 18c4442ef..5018a77cb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -151,7 +151,7 @@ github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/host github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process -# github.com/containers/storage v1.19.0 +# github.com/containers/storage v1.19.1 github.com/containers/storage github.com/containers/storage/drivers github.com/containers/storage/drivers/aufs @@ -321,7 +321,7 @@ github.com/inconshreveable/mousetrap github.com/ishidawataru/sctp # github.com/json-iterator/go v1.1.9 github.com/json-iterator/go -# github.com/klauspost/compress v1.10.4 +# github.com/klauspost/compress v1.10.5 github.com/klauspost/compress/flate github.com/klauspost/compress/fse github.com/klauspost/compress/huff0 |