From d65ff6b3ec18aad6a64329c54a83d5ba5d51b62f Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Sun, 2 Feb 2020 09:39:12 -0600 Subject: apiv2 container create using specgen this uses the specgen structure to create containers rather than the outdated createconfig. right now, only the apiv2 create is wired up. eventually the cli will also have to be done. Signed-off-by: Brent Baude --- pkg/api/handlers/containers_create.go | 240 -------------------------- pkg/api/handlers/generic/containers_create.go | 216 +++++++++++++++++++++++ pkg/api/handlers/generic/swagger.go | 6 +- pkg/api/handlers/libpod/containers_create.go | 29 ++++ pkg/api/handlers/libpod/pods.go | 4 +- pkg/api/handlers/types.go | 9 +- pkg/api/handlers/utils/containers.go | 25 +++ 7 files changed, 276 insertions(+), 253 deletions(-) delete mode 100644 pkg/api/handlers/containers_create.go create mode 100644 pkg/api/handlers/generic/containers_create.go create mode 100644 pkg/api/handlers/libpod/containers_create.go (limited to 'pkg/api/handlers') diff --git a/pkg/api/handlers/containers_create.go b/pkg/api/handlers/containers_create.go deleted file mode 100644 index 48f0de94d..000000000 --- a/pkg/api/handlers/containers_create.go +++ /dev/null @@ -1,240 +0,0 @@ -package handlers - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" - image2 "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/api/handlers/utils" - "github.com/containers/libpod/pkg/namespaces" - "github.com/containers/libpod/pkg/signal" - createconfig "github.com/containers/libpod/pkg/spec" - "github.com/containers/storage" - "github.com/gorilla/schema" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -func CreateContainer(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value("runtime").(*libpod.Runtime) - decoder := r.Context().Value("decoder").(*schema.Decoder) - input := CreateContainerConfig{} - query := struct { - Name string `schema:"name"` - }{ - // override any golang type defaults - } - 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 - } - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) - return - } - if len(input.HostConfig.Links) > 0 { - utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) - } - newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) - return - } - cc, err := makeCreateConfig(input, newImage) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) - return - } - - cc.Name = query.Name - var pod *libpod.Pod - ctr, err := shared.CreateContainerFromCreateConfig(runtime, &cc, r.Context(), pod) - if err != nil { - if strings.Contains(err.Error(), "invalid log driver") { - // this does not quite work yet and needs a little more massaging - w.Header().Set("Content-Type", "text/plain; charset=us-ascii") - w.WriteHeader(http.StatusInternalServerError) - msg := fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type) - if _, err := fmt.Fprintln(w, msg); err != nil { - log.Errorf("%s: %q", msg, err) - } - //s.WriteResponse(w, http.StatusInternalServerError, fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type)) - return - } - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()")) - return - } - - response := ContainerCreateResponse{ - ID: ctr.ID(), - Warnings: []string{}} - - utils.WriteResponse(w, http.StatusCreated, response) -} - -func makeCreateConfig(input CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { - var ( - err error - init bool - tmpfs []string - volumes []string - ) - env := make(map[string]string) - stopSignal := unix.SIGTERM - if len(input.StopSignal) > 0 { - stopSignal, err = signal.ParseSignal(input.StopSignal) - if err != nil { - return createconfig.CreateConfig{}, err - } - } - - workDir := "/" - if len(input.WorkingDir) > 0 { - workDir = input.WorkingDir - } - - stopTimeout := uint(define.CtrRemoveTimeout) - if input.StopTimeout != nil { - stopTimeout = uint(*input.StopTimeout) - } - c := createconfig.CgroupConfig{ - Cgroups: "", // podman - Cgroupns: "", // podman - CgroupParent: "", // podman - CgroupMode: "", // podman - } - security := createconfig.SecurityConfig{ - CapAdd: input.HostConfig.CapAdd, - CapDrop: input.HostConfig.CapDrop, - LabelOpts: nil, // podman - NoNewPrivs: false, // podman - ApparmorProfile: "", // podman - SeccompProfilePath: "", - SecurityOpts: input.HostConfig.SecurityOpt, - Privileged: input.HostConfig.Privileged, - ReadOnlyRootfs: input.HostConfig.ReadonlyRootfs, - ReadOnlyTmpfs: false, // podman-only - Sysctl: input.HostConfig.Sysctls, - } - - network := createconfig.NetworkConfig{ - DNSOpt: input.HostConfig.DNSOptions, - DNSSearch: input.HostConfig.DNSSearch, - DNSServers: input.HostConfig.DNS, - ExposedPorts: input.ExposedPorts, - HTTPProxy: false, // podman - IP6Address: "", - IPAddress: "", - LinkLocalIP: nil, // docker-only - MacAddress: input.MacAddress, - // NetMode: nil, - Network: input.HostConfig.NetworkMode.NetworkName(), - NetworkAlias: nil, // docker-only now - PortBindings: input.HostConfig.PortBindings, - Publish: nil, // podmanseccompPath - PublishAll: input.HostConfig.PublishAllPorts, - } - - uts := createconfig.UtsConfig{ - UtsMode: namespaces.UTSMode(input.HostConfig.UTSMode), - NoHosts: false, //podman - HostAdd: input.HostConfig.ExtraHosts, - Hostname: input.Hostname, - } - - z := createconfig.UserConfig{ - GroupAdd: input.HostConfig.GroupAdd, - IDMappings: &storage.IDMappingOptions{}, // podman //TODO <--- fix this, - UsernsMode: namespaces.UsernsMode(input.HostConfig.UsernsMode), - User: input.User, - } - pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)} - for k := range input.Volumes { - volumes = append(volumes, k) - } - - // Docker is more flexible about its input where podman throws - // away incorrectly formatted variables so we cannot reuse the - // parsing of the env input - // [Foo Other=one Blank=] - for _, e := range input.Env { - splitEnv := strings.Split(e, "=") - switch len(splitEnv) { - case 0: - continue - case 1: - env[splitEnv[0]] = "" - default: - env[splitEnv[0]] = strings.Join(splitEnv[1:], "=") - } - } - - // format the tmpfs mounts into a []string from map - for k, v := range input.HostConfig.Tmpfs { - tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", k, v)) - } - - if input.HostConfig.Init != nil && *input.HostConfig.Init { - init = true - } - - m := createconfig.CreateConfig{ - Annotations: nil, // podman - Args: nil, - Cgroup: c, - CidFile: "", - ConmonPidFile: "", // podman - Command: input.Cmd, - UserCommand: input.Cmd, // podman - Detach: false, // - // Devices: input.HostConfig.Devices, - Entrypoint: input.Entrypoint, - Env: env, - HealthCheck: nil, // - Init: init, - InitPath: "", // tbd - Image: input.Image, - ImageID: newImage.ID(), - BuiltinImgVolumes: nil, // podman - ImageVolumeType: "", // podman - Interactive: false, - // IpcMode: input.HostConfig.IpcMode, - Labels: input.Labels, - LogDriver: input.HostConfig.LogConfig.Type, // is this correct - // LogDriverOpt: input.HostConfig.LogConfig.Config, - Name: input.Name, - Network: network, - Pod: "", // podman - PodmanPath: "", // podman - Quiet: false, // front-end only - Resources: createconfig.CreateResourceConfig{}, - RestartPolicy: input.HostConfig.RestartPolicy.Name, - Rm: input.HostConfig.AutoRemove, - StopSignal: stopSignal, - StopTimeout: stopTimeout, - Systemd: false, // podman - Tmpfs: tmpfs, - User: z, - Uts: uts, - Tty: input.Tty, - Mounts: nil, // we populate - // MountsFlag: input.HostConfig.Mounts, - NamedVolumes: nil, // we populate - Volumes: volumes, - VolumesFrom: input.HostConfig.VolumesFrom, - WorkDir: workDir, - Rootfs: "", // podman - Security: security, - Syslog: false, // podman - - Pid: pidConfig, - } - return m, nil -} diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/generic/containers_create.go new file mode 100644 index 000000000..7e542752f --- /dev/null +++ b/pkg/api/handlers/generic/containers_create.go @@ -0,0 +1,216 @@ +package generic + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + image2 "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/signal" + createconfig "github.com/containers/libpod/pkg/spec" + "github.com/containers/storage" + "github.com/gorilla/schema" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func CreateContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + input := handlers.CreateContainerConfig{} + query := struct { + Name string `schema:"name"` + }{ + // override any golang type defaults + } + 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 + } + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + if len(input.HostConfig.Links) > 0 { + utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) + } + newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) + return + } + cc, err := makeCreateConfig(input, newImage) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) + return + } + cc.Name = query.Name + utils.CreateContainer(r.Context(), w, runtime, &cc) +} + +func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { + var ( + err error + init bool + tmpfs []string + volumes []string + ) + env := make(map[string]string) + stopSignal := unix.SIGTERM + if len(input.StopSignal) > 0 { + stopSignal, err = signal.ParseSignal(input.StopSignal) + if err != nil { + return createconfig.CreateConfig{}, err + } + } + + workDir := "/" + if len(input.WorkingDir) > 0 { + workDir = input.WorkingDir + } + + stopTimeout := uint(define.CtrRemoveTimeout) + if input.StopTimeout != nil { + stopTimeout = uint(*input.StopTimeout) + } + c := createconfig.CgroupConfig{ + Cgroups: "", // podman + Cgroupns: "", // podman + CgroupParent: "", // podman + CgroupMode: "", // podman + } + security := createconfig.SecurityConfig{ + CapAdd: input.HostConfig.CapAdd, + CapDrop: input.HostConfig.CapDrop, + LabelOpts: nil, // podman + NoNewPrivs: false, // podman + ApparmorProfile: "", // podman + SeccompProfilePath: "", + SecurityOpts: input.HostConfig.SecurityOpt, + Privileged: input.HostConfig.Privileged, + ReadOnlyRootfs: input.HostConfig.ReadonlyRootfs, + ReadOnlyTmpfs: false, // podman-only + Sysctl: input.HostConfig.Sysctls, + } + + network := createconfig.NetworkConfig{ + DNSOpt: input.HostConfig.DNSOptions, + DNSSearch: input.HostConfig.DNSSearch, + DNSServers: input.HostConfig.DNS, + ExposedPorts: input.ExposedPorts, + HTTPProxy: false, // podman + IP6Address: "", + IPAddress: "", + LinkLocalIP: nil, // docker-only + MacAddress: input.MacAddress, + // NetMode: nil, + Network: input.HostConfig.NetworkMode.NetworkName(), + NetworkAlias: nil, // docker-only now + PortBindings: input.HostConfig.PortBindings, + Publish: nil, // podmanseccompPath + PublishAll: input.HostConfig.PublishAllPorts, + } + + uts := createconfig.UtsConfig{ + UtsMode: namespaces.UTSMode(input.HostConfig.UTSMode), + NoHosts: false, //podman + HostAdd: input.HostConfig.ExtraHosts, + Hostname: input.Hostname, + } + + z := createconfig.UserConfig{ + GroupAdd: input.HostConfig.GroupAdd, + IDMappings: &storage.IDMappingOptions{}, // podman //TODO <--- fix this, + UsernsMode: namespaces.UsernsMode(input.HostConfig.UsernsMode), + User: input.User, + } + pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)} + for k := range input.Volumes { + volumes = append(volumes, k) + } + + // Docker is more flexible about its input where podman throws + // away incorrectly formatted variables so we cannot reuse the + // parsing of the env input + // [Foo Other=one Blank=] + for _, e := range input.Env { + splitEnv := strings.Split(e, "=") + switch len(splitEnv) { + case 0: + continue + case 1: + env[splitEnv[0]] = "" + default: + env[splitEnv[0]] = strings.Join(splitEnv[1:], "=") + } + } + + // format the tmpfs mounts into a []string from map + for k, v := range input.HostConfig.Tmpfs { + tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", k, v)) + } + + if input.HostConfig.Init != nil && *input.HostConfig.Init { + init = true + } + + m := createconfig.CreateConfig{ + Annotations: nil, // podman + Args: nil, + Cgroup: c, + CidFile: "", + ConmonPidFile: "", // podman + Command: input.Cmd, + UserCommand: input.Cmd, // podman + Detach: false, // + // Devices: input.HostConfig.Devices, + Entrypoint: input.Entrypoint, + Env: env, + HealthCheck: nil, // + Init: init, + InitPath: "", // tbd + Image: input.Image, + ImageID: newImage.ID(), + BuiltinImgVolumes: nil, // podman + ImageVolumeType: "", // podman + Interactive: false, + // IpcMode: input.HostConfig.IpcMode, + Labels: input.Labels, + LogDriver: input.HostConfig.LogConfig.Type, // is this correct + // LogDriverOpt: input.HostConfig.LogConfig.Config, + Name: input.Name, + Network: network, + Pod: "", // podman + PodmanPath: "", // podman + Quiet: false, // front-end only + Resources: createconfig.CreateResourceConfig{}, + RestartPolicy: input.HostConfig.RestartPolicy.Name, + Rm: input.HostConfig.AutoRemove, + StopSignal: stopSignal, + StopTimeout: stopTimeout, + Systemd: false, // podman + Tmpfs: tmpfs, + User: z, + Uts: uts, + Tty: input.Tty, + Mounts: nil, // we populate + // MountsFlag: input.HostConfig.Mounts, + NamedVolumes: nil, // we populate + Volumes: volumes, + VolumesFrom: input.HostConfig.VolumesFrom, + WorkDir: workDir, + Rootfs: "", // podman + Security: security, + Syslog: false, // podman + + Pid: pidConfig, + } + return m, nil +} diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go index bfe527c41..c9c9610bb 100644 --- a/pkg/api/handlers/generic/swagger.go +++ b/pkg/api/handlers/generic/swagger.go @@ -1,13 +1,15 @@ package generic -import "github.com/containers/libpod/pkg/api/handlers" +import ( + "github.com/containers/libpod/pkg/api/handlers/utils" +) // Create container // swagger:response ContainerCreateResponse type swagCtrCreateResponse struct { // in:body Body struct { - handlers.ContainerCreateResponse + utils.ContainerCreateResponse } } diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go new file mode 100644 index 000000000..ebca41151 --- /dev/null +++ b/pkg/api/handlers/libpod/containers_create.go @@ -0,0 +1,29 @@ +package libpod + +import ( + "encoding/json" + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" +) + +// CreateContainer takes a specgenerator and makes a container. It returns +// the new container ID on success along with any warnings. +func CreateContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + var sg specgen.SpecGenerator + if err := json.NewDecoder(r.Body).Decode(&sg); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + ctr, err := sg.MakeContainer(runtime) + if err != nil { + utils.InternalServerError(w, err) + return + } + response := utils.ContainerCreateResponse{ID: ctr.ID()} + utils.WriteJSON(w, http.StatusCreated, response) +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index d043b1204..008b9b14b 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -99,12 +99,10 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http_code, err) return } - utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()}) + utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.ID()}) } func Pods(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 internal var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) podInspectData []*libpod.PodInspect diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index a50f183f7..6268028f5 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -151,6 +151,7 @@ type ContainerTopOKBody struct { dockerContainer.ContainerTopOKBody } +// swagger:model PodCreateConfig type PodCreateConfig struct { Name string `json:"name"` CGroupParent string `json:"cgroup-parent"` @@ -548,11 +549,3 @@ func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) { } return ports, nil } - -// ContainerCreateResponse is the response struct for creating a container -type ContainerCreateResponse struct { - // ID of the container created - ID string `json:"id"` - // Warnings during container creation - Warnings []string `json:"Warnings"` -} diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index c9bb9cf09..402005581 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -1,6 +1,7 @@ package utils import ( + "context" "fmt" "net/http" "syscall" @@ -9,10 +10,19 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + createconfig "github.com/containers/libpod/pkg/spec" "github.com/gorilla/schema" "github.com/pkg/errors" ) +// ContainerCreateResponse is the response struct for creating a container +type ContainerCreateResponse struct { + // ID of the container created + ID string `json:"id"` + // Warnings during container creation + Warnings []string `json:"Warnings"` +} + func KillContainer(w http.ResponseWriter, r *http.Request) (*libpod.Container, error) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -119,3 +129,18 @@ func GenerateFilterFuncsFromMap(r *libpod.Runtime, filters map[string][]string) } return filterFuncs, nil } + +func CreateContainer(ctx context.Context, w http.ResponseWriter, runtime *libpod.Runtime, cc *createconfig.CreateConfig) { + var pod *libpod.Pod + ctr, err := shared.CreateContainerFromCreateConfig(runtime, cc, ctx, pod) + if err != nil { + Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()")) + return + } + + response := ContainerCreateResponse{ + ID: ctr.ID(), + Warnings: []string{}} + + WriteResponse(w, http.StatusCreated, response) +} -- cgit v1.2.3-54-g00ecf