diff options
Diffstat (limited to 'pkg')
72 files changed, 1612 insertions, 323 deletions
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go new file mode 100644 index 000000000..c3a26873e --- /dev/null +++ b/pkg/api/handlers/compat/containers_archive.go @@ -0,0 +1,12 @@ +package compat + +import ( + "errors" + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/utils" +) + +func Archive(w http.ResponseWriter, r *http.Request) { + utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented")) +} diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index 012e20daf..990140ee1 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -90,7 +90,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { // For Docker compatibility, we need to re-initialize containers in these states. if state == define.ContainerStateConfigured || state == define.ContainerStateExited { if err := ctr.Init(r.Context()); err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID())) + utils.Error(w, "Container in wrong state", http.StatusConflict, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID())) return } } else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go index 62ccd2b93..048321add 100644 --- a/pkg/api/handlers/compat/containers_stats.go +++ b/pkg/api/handlers/compat/containers_stats.go @@ -45,8 +45,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - if state != define.ContainerStateRunning && !query.Stream { - utils.InternalServerError(w, define.ErrCtrStateInvalid) + if state != define.ContainerStateRunning { + utils.Error(w, "Container not running and streaming requested", http.StatusConflict, define.ErrCtrStateInvalid) return } diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 7ebfb0d1e..577ddd0a1 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -26,7 +26,10 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { Since string `schema:"since"` Until string `schema:"until"` Filters map[string][]string `schema:"filters"` - }{} + Stream bool `schema:"stream"` + }{ + Stream: true, + } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) } @@ -41,9 +44,10 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { if len(query.Since) > 0 || len(query.Until) > 0 { fromStart = true } + eventChannel := make(chan *events.Event) go func() { - readOpts := events.ReadOptions{FromStart: fromStart, Stream: true, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until} + readOpts := events.ReadOptions{FromStart: fromStart, Stream: query.Stream, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until} eventsError = runtime.Events(readOpts) }() if eventsError != nil { @@ -55,7 +59,9 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { // If client disappears we need to stop listening for events go func(done <-chan struct{}) { <-done - close(eventChannel) + if _, ok := <-eventChannel; ok { + close(eventChannel) + } }(r.Context().Done()) // Headers need to be written out before turning Writer() over to json encoder diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index ea9cbd691..b64ed0036 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -15,6 +15,7 @@ import ( 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/auth" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/docker/docker/api/types" @@ -251,19 +252,32 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { return } - /* - fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed. - repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image. - tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled. - */ fromImage := query.FromImage if len(query.Tag) >= 1 { fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag) } - // TODO - // We are eating the output right now because we haven't talked about how to deal with multiple responses yet - img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing) + authConf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String())) + return + } + defer auth.RemoveAuthfile(authfile) + + registryOpts := image2.DockerRegistryOptions{DockerRegistryCreds: authConf} + if sys := runtime.SystemContext(); sys != nil { + registryOpts.DockerCertPath = sys.DockerCertPath + } + img, err := runtime.ImageRuntime().New(r.Context(), + fromImage, + "", // signature policy + authfile, + nil, // writer + ®istryOpts, + image2.SigningOptions{}, + nil, // label + util.PullImageMissing, + ) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go index 2260d5557..47976b7c9 100644 --- a/pkg/api/handlers/compat/images_push.go +++ b/pkg/api/handlers/compat/images_push.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/auth" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -48,13 +49,17 @@ func PushImage(w http.ResponseWriter, r *http.Request) { return } - // TODO: the X-Registry-Auth header is not checked yet here nor in any other - // endpoint. Pushing does NOT work with authentication at the moment. - dockerRegistryOptions := &image.DockerRegistryOptions{} - authfile := "" + authConf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String())) + return + } + defer auth.RemoveAuthfile(authfile) + + dockerRegistryOptions := &image.DockerRegistryOptions{DockerRegistryCreds: authConf} if sys := runtime.SystemContext(); sys != nil { dockerRegistryOptions.DockerCertPath = sys.DockerCertPath - authfile = sys.AuthFilePath + dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath } err = newImage.PushImageToHeuristicDestination( diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go new file mode 100644 index 000000000..c52ca093f --- /dev/null +++ b/pkg/api/handlers/compat/networks.go @@ -0,0 +1,301 @@ +package compat + +import ( + "encoding/json" + "net" + "net/http" + "os" + "syscall" + "time" + + "github.com/containernetworking/cni/libcni" + "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/containers/libpod/pkg/network" + "github.com/docker/docker/api/types" + dockerNetwork "github.com/docker/docker/api/types/network" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +type CompatInspectNetwork struct { + types.NetworkResource +} + +func InspectNetwork(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + // FYI scope and version are currently unused but are described by the API + // Leaving this for if/when we have to enable these + //query := struct { + // scope string + // verbose bool + //}{ + // // override any golang type defaults + //} + //decoder := r.Context().Value("decoder").(*schema.Decoder) + //if err := decoder.Decode(&query, r.URL.Query()); err != nil { + // utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + // return + //} + config, err := runtime.GetConfig() + if err != nil { + utils.InternalServerError(w, err) + return + } + name := utils.GetName(r) + _, err = network.InspectNetwork(config, name) + if err != nil { + // TODO our network package does not distinguish between not finding a + // specific network vs not being able to read it + utils.InternalServerError(w, err) + return + } + report, err := getNetworkResourceByName(name, runtime) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, report) +} + +func getNetworkResourceByName(name string, runtime *libpod.Runtime) (*types.NetworkResource, error) { + var ( + ipamConfigs []dockerNetwork.IPAMConfig + ) + config, err := runtime.GetConfig() + if err != nil { + return nil, err + } + containerEndpoints := map[string]types.EndpointResource{} + // Get the network path so we can get created time + networkConfigPath, err := network.GetCNIConfigPathByName(config, name) + if err != nil { + return nil, err + } + f, err := os.Stat(networkConfigPath) + if err != nil { + return nil, err + } + stat := f.Sys().(*syscall.Stat_t) + cons, err := runtime.GetAllContainers() + if err != nil { + return nil, err + } + conf, err := libcni.ConfListFromFile(networkConfigPath) + if err != nil { + return nil, err + } + + // No Bridge plugin means we bail + bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver) + if err != nil { + return nil, err + } + for _, outer := range bridge.IPAM.Ranges { + for _, n := range outer { + ipamConfig := dockerNetwork.IPAMConfig{ + Subnet: n.Subnet, + Gateway: n.Gateway, + } + ipamConfigs = append(ipamConfigs, ipamConfig) + } + } + + for _, con := range cons { + data, err := con.Inspect(false) + if err != nil { + return nil, err + } + if netData, ok := data.NetworkSettings.Networks[name]; ok { + containerEndpoint := types.EndpointResource{ + Name: netData.NetworkID, + EndpointID: netData.EndpointID, + MacAddress: netData.MacAddress, + IPv4Address: netData.IPAddress, + IPv6Address: netData.GlobalIPv6Address, + } + containerEndpoints[con.ID()] = containerEndpoint + } + } + report := types.NetworkResource{ + Name: name, + ID: "", + Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert + Scope: "", + Driver: network.DefaultNetworkDriver, + EnableIPv6: false, + IPAM: dockerNetwork.IPAM{ + Driver: "default", + Options: nil, + Config: ipamConfigs, + }, + Internal: false, + Attachable: false, + Ingress: false, + ConfigFrom: dockerNetwork.ConfigReference{}, + ConfigOnly: false, + Containers: containerEndpoints, + Options: nil, + Labels: nil, + Peers: nil, + Services: nil, + } + return &report, nil +} + +func genericPluginsToBridge(plugins []*libcni.NetworkConfig, pluginType string) (network.HostLocalBridge, error) { + var bridge network.HostLocalBridge + generic, err := findPluginByName(plugins, pluginType) + if err != nil { + return bridge, err + } + err = json.Unmarshal(generic, &bridge) + return bridge, err +} + +func findPluginByName(plugins []*libcni.NetworkConfig, pluginType string) ([]byte, error) { + for _, p := range plugins { + if pluginType == p.Network.Type { + return p.Bytes, nil + } + } + return nil, errors.New("unable to find bridge plugin") +} + +func ListNetworks(w http.ResponseWriter, r *http.Request) { + var ( + reports []*types.NetworkResource + ) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Filters map[string][]string `schema:"filters"` + }{ + // override any golang type defaults + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + config, err := runtime.GetConfig() + if err != nil { + utils.InternalServerError(w, err) + return + } + // TODO remove when filters are implemented + if len(query.Filters) > 0 { + utils.InternalServerError(w, errors.New("filters for listing networks is not implemented")) + return + } + netNames, err := network.GetNetworkNamesFromFileSystem(config) + if err != nil { + utils.InternalServerError(w, err) + return + } + for _, name := range netNames { + report, err := getNetworkResourceByName(name, runtime) + if err != nil { + utils.InternalServerError(w, err) + } + reports = append(reports, report) + } + utils.WriteResponse(w, http.StatusOK, reports) +} + +func CreateNetwork(w http.ResponseWriter, r *http.Request) { + var ( + name string + networkCreate types.NetworkCreateRequest + ) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + + if len(networkCreate.Name) > 0 { + name = networkCreate.Name + } + // At present I think we should just suport the bridge driver + // and allow demand to make us consider more + if networkCreate.Driver != network.DefaultNetworkDriver { + utils.InternalServerError(w, errors.New("network create only supports the bridge driver")) + return + } + ncOptions := entities.NetworkCreateOptions{ + Driver: network.DefaultNetworkDriver, + Internal: networkCreate.Internal, + } + if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil { + if len(networkCreate.IPAM.Config) > 1 { + utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config")) + return + } + + if len(networkCreate.IPAM.Config[0].Subnet) > 0 { + _, subnet, err := net.ParseCIDR(networkCreate.IPAM.Config[0].Subnet) + if err != nil { + utils.InternalServerError(w, err) + return + } + ncOptions.Subnet = *subnet + } + if len(networkCreate.IPAM.Config[0].Gateway) > 0 { + ncOptions.Gateway = net.ParseIP(networkCreate.IPAM.Config[0].Gateway) + } + if len(networkCreate.IPAM.Config[0].IPRange) > 0 { + _, IPRange, err := net.ParseCIDR(networkCreate.IPAM.Config[0].IPRange) + if err != nil { + utils.InternalServerError(w, err) + return + } + ncOptions.Range = *IPRange + } + } + ce := abi.ContainerEngine{Libpod: runtime} + _, err := ce.NetworkCreate(r.Context(), name, ncOptions) + if err != nil { + utils.InternalServerError(w, err) + } + report := types.NetworkCreate{ + CheckDuplicate: networkCreate.CheckDuplicate, + Driver: networkCreate.Driver, + Scope: networkCreate.Scope, + EnableIPv6: networkCreate.EnableIPv6, + IPAM: networkCreate.IPAM, + Internal: networkCreate.Internal, + Attachable: networkCreate.Attachable, + Ingress: networkCreate.Ingress, + ConfigOnly: networkCreate.ConfigOnly, + ConfigFrom: networkCreate.ConfigFrom, + Options: networkCreate.Options, + Labels: networkCreate.Labels, + } + utils.WriteResponse(w, http.StatusOK, report) +} + +func RemoveNetwork(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + config, err := runtime.GetConfig() + if err != nil { + utils.InternalServerError(w, err) + return + } + name := utils.GetName(r) + exists, err := network.Exists(config, name) + if err != nil { + utils.InternalServerError(w, err) + return + } + if !exists { + utils.Error(w, "network not found", http.StatusNotFound, err) + return + } + if err := network.RemoveNetwork(config, name); err != nil { + utils.InternalServerError(w, err) + } + utils.WriteResponse(w, http.StatusNoContent, "") +} diff --git a/pkg/api/handlers/compat/resize.go b/pkg/api/handlers/compat/resize.go index 3ead733bc..231b53175 100644 --- a/pkg/api/handlers/compat/resize.go +++ b/pkg/api/handlers/compat/resize.go @@ -1,10 +1,12 @@ package compat import ( + "fmt" "net/http" "strings" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -43,6 +45,14 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) { utils.ContainerNotFound(w, name, err) return } + if state, err := ctnr.State(); err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain container state")) + return + } else if state != define.ContainerStateRunning { + utils.Error(w, "Container not running", http.StatusConflict, + fmt.Errorf("container %q in wrong state %q", name, state.String())) + return + } if err := ctnr.AttachResize(sz); err != nil { utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container")) return @@ -56,6 +66,14 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) { utils.SessionNotFound(w, name, err) return } + if state, err := ctnr.State(); err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain session container state")) + return + } else if state != define.ContainerStateRunning { + utils.Error(w, "Container not running", http.StatusConflict, + fmt.Errorf("container %q in wrong state %q", name, state.String())) + return + } if err := ctnr.ExecResize(name, sz); err != nil { utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session")) return diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go index ce83aa32f..dc94a7ebd 100644 --- a/pkg/api/handlers/compat/swagger.go +++ b/pkg/api/handlers/compat/swagger.go @@ -3,6 +3,7 @@ package compat import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/storage/pkg/archive" + "github.com/docker/docker/api/types" ) // Create container @@ -35,3 +36,30 @@ type swagChangesResponse struct { Changes []archive.Change } } + +// Network inspect +// swagger:response CompatNetworkInspect +type swagCompatNetworkInspect struct { + // in:body + Body types.NetworkResource +} + +// Network list +// swagger:response CompatNetworkList +type swagCompatNetworkList struct { + // in:body + Body []types.NetworkResource +} + +// Network create +// swagger:model NetworkCreateRequest +type NetworkCreateRequest struct { + types.NetworkCreateRequest +} + +// Network create +// swagger:response CompatNetworkCreate +type swagCompatNetworkCreateResponse struct { + // in:body + Body struct{ types.NetworkCreate } +} diff --git a/pkg/api/handlers/compat/types.go b/pkg/api/handlers/compat/types.go index b8d06760f..6d47ede64 100644 --- a/pkg/api/handlers/compat/types.go +++ b/pkg/api/handlers/compat/types.go @@ -48,7 +48,7 @@ type StatsJSON struct { Stats Name string `json:"name,omitempty"` - ID string `json:"id,omitempty"` + ID string `json:"Id,omitempty"` // Networks request version >=1.21 Networks map[string]docker.NetworkStats `json:"networks,omitempty"` diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 3902bdc9b..50f6b1a38 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -66,6 +66,10 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } + if len(pss) == 0 { + utils.WriteResponse(w, http.StatusOK, "[]") + return + } utils.WriteResponse(w, http.StatusOK, pss) } diff --git a/pkg/api/handlers/libpod/copy.go b/pkg/api/handlers/libpod/copy.go new file mode 100644 index 000000000..a3b404bce --- /dev/null +++ b/pkg/api/handlers/libpod/copy.go @@ -0,0 +1,12 @@ +package libpod + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" +) + +func Archive(w http.ResponseWriter, r *http.Request) { + utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented")) +} diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 1cbcfb52c..4b277d39c 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -21,6 +21,7 @@ import ( 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/auth" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra/abi" "github.com/containers/libpod/pkg/errorhandling" @@ -28,6 +29,7 @@ import ( utils2 "github.com/containers/libpod/utils" "github.com/gorilla/schema" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // Commit @@ -339,7 +341,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Reference string `schema:"reference"` - Credentials string `schema:"credentials"` OverrideOS string `schema:"overrideOS"` OverrideArch string `schema:"overrideArch"` TLSVerify bool `schema:"tlsVerify"` @@ -382,20 +383,16 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { return } - var registryCreds *types.DockerAuthConfig - if len(query.Credentials) != 0 { - creds, err := util.ParseRegistryCreds(query.Credentials) - if err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "error parsing credentials %q", query.Credentials)) - return - } - registryCreds = creds + authConf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String())) + return } + defer auth.RemoveAuthfile(authfile) // Setup the registry options dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, + DockerRegistryCreds: authConf, OSChoice: query.OverrideOS, ArchitectureChoice: query.OverrideArch, } @@ -403,6 +400,13 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } + sys := runtime.SystemContext() + if sys == nil { + sys = image.GetSystemContext("", authfile, false) + } + dockerRegistryOptions.DockerCertPath = sys.DockerCertPath + sys.DockerAuthConfig = authConf + // Prepare the images we want to pull imagesToPull := []string{} res := []handlers.LibpodImagesPullReport{} @@ -411,8 +415,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { if !query.AllTags { imagesToPull = append(imagesToPull, imageName) } else { - systemContext := image.GetSystemContext("", "", false) - tags, err := docker.GetRepositoryTags(context.Background(), systemContext, imageRef) + tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef) if err != nil { utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags")) return @@ -422,12 +425,6 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { } } - authfile := "" - if sys := runtime.SystemContext(); sys != nil { - dockerRegistryOptions.DockerCertPath = sys.DockerCertPath - authfile = sys.AuthFilePath - } - // Finally pull the images for _, img := range imagesToPull { newImage, err := runtime.ImageRuntime().New( @@ -456,7 +453,6 @@ func PushImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - Credentials string `schema:"credentials"` Destination string `schema:"destination"` TLSVerify bool `schema:"tlsVerify"` }{ @@ -492,26 +488,20 @@ func PushImage(w http.ResponseWriter, r *http.Request) { return } - var registryCreds *types.DockerAuthConfig - if len(query.Credentials) != 0 { - creds, err := util.ParseRegistryCreds(query.Credentials) - if err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "error parsing credentials %q", query.Credentials)) - return - } - registryCreds = creds + authConf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String())) + return } + defer auth.RemoveAuthfile(authfile) + logrus.Errorf("AuthConf: %v", authConf) - // TODO: the X-Registry-Auth header is not checked yet here nor in any other - // endpoint. Pushing does NOT work with authentication at the moment. dockerRegistryOptions := &image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, + DockerRegistryCreds: authConf, } - authfile := "" if sys := runtime.SystemContext(); sys != nil { dockerRegistryOptions.DockerCertPath = sys.DockerCertPath - authfile = sys.AuthFilePath + dockerRegistryOptions.RegistriesConfPath = sys.SystemRegistriesConfPath } if _, found := r.URL.Query()["tlsVerify"]; found { dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 93ca367f7..aef92368b 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -120,6 +120,10 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID}) } func ManifestPush(w http.ResponseWriter, r *http.Request) { + // FIXME: parameters are missing (tlsVerify, format). + // Also, we should use the ABI function to avoid duplicate code. + // Also, support for XRegistryAuth headers are missing. + runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index 26e02bf4f..1cb5cdb6c 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -9,6 +9,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/auth" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra/abi" "github.com/gorilla/schema" @@ -47,9 +48,26 @@ func PlayKube(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file")) return } + authConf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String())) + return + } + defer auth.RemoveAuthfile(authfile) + var username, password string + if authConf != nil { + username = authConf.Username + password = authConf.Password + } containerEngine := abi.ContainerEngine{Libpod: runtime} - options := entities.PlayKubeOptions{Network: query.Network, Quiet: true} + options := entities.PlayKubeOptions{ + Authfile: authfile, + Username: username, + Password: password, + Network: query.Network, + Quiet: true, + } if _, found := r.URL.Query()["tlsVerify"]; found { options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index d8cdd9caf..aa3d0fe91 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -120,7 +120,7 @@ type CreateContainerConfig struct { // swagger:model IDResponse type IDResponse struct { // ID - ID string `json:"id"` + ID string `json:"Id"` } type ContainerTopOKBody struct { diff --git a/pkg/api/server/register_archive.go b/pkg/api/server/register_archive.go new file mode 100644 index 000000000..a1d5941bc --- /dev/null +++ b/pkg/api/server/register_archive.go @@ -0,0 +1,171 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/compat" + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerAchiveHandlers(r *mux.Router) error { + // swagger:operation POST /containers/{name}/archive compat putArchive + // --- + // summary: Put files into a container + // description: Put a tar archive of files into a container + // tags: + // - containers (compat) + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // description: container name or id + // required: true + // - in: query + // name: path + // type: string + // description: Path to a directory in the container to extract + // required: true + // - in: query + // name: noOverwriteDirNonDir + // type: string + // description: if unpacking the given content would cause an existing directory to be replaced with a non-directory and vice versa (1 or true) + // - in: query + // name: copyUIDGID + // type: string + // description: copy UID/GID maps to the dest file or di (1 or true) + // - in: body + // name: request + // description: tarfile of files to copy into the container + // schema: + // type: string + // responses: + // 200: + // description: no error + // 400: + // $ref: "#/responses/BadParamError" + // 403: + // description: the container rootfs is read-only + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + + // swagger:operation GET /containers/{name}/archive compat getArchive + // --- + // summary: Get files from a container + // description: Get a tar archive of files from a container + // tags: + // - containers (compat) + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // description: container name or id + // required: true + // - in: query + // name: path + // type: string + // description: Path to a directory in the container to extract + // required: true + // responses: + // 200: + // description: no error + // schema: + // type: string + // format: binary + // 400: + // $ref: "#/responses/BadParamError" + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name}/archive"), s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPost) + // Added non version path to URI to support docker non versioned paths + r.HandleFunc("/containers/{name}/archive", s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPost) + + /* + Libpod + */ + + // swagger:operation POST /libpod/containers/{name}/copy libpod libpodPutArchive + // --- + // summary: Copy files into a container + // description: Copy a tar archive of files into a container + // tags: + // - containers + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // description: container name or id + // required: true + // - in: query + // name: path + // type: string + // description: Path to a directory in the container to extract + // required: true + // - in: query + // name: pause + // type: boolean + // description: pause the container while copying (defaults to true) + // default: true + // - in: body + // name: request + // description: tarfile of files to copy into the container + // schema: + // type: string + // responses: + // 200: + // description: no error + // 400: + // $ref: "#/responses/BadParamError" + // 403: + // description: the container rootfs is read-only + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + + // swagger:operation GET /libpod/containers/{name}/copy libpod libpodGetArchive + // --- + // summary: Copy files from a container + // description: Copy a tar archive of files from a container + // tags: + // - containers (compat) + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // description: container name or id + // required: true + // - in: query + // name: path + // type: string + // description: Path to a directory in the container to extract + // required: true + // responses: + // 200: + // description: no error + // schema: + // type: string + // format: binary + // 400: + // $ref: "#/responses/BadParamError" + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name}/copy"), s.APIHandler(libpod.Archive)).Methods(http.MethodGet, http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name}/archive"), s.APIHandler(libpod.Archive)).Methods(http.MethodGet, http.MethodPost) + + return nil +} diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go index e909303da..2b85eb169 100644 --- a/pkg/api/server/register_events.go +++ b/pkg/api/server/register_events.go @@ -58,6 +58,11 @@ func (s *APIServer) registerEventsHandlers(r *mux.Router) error { // type: string // in: query // description: JSON encoded map[string][]string of constraints + // - name: stream + // type: boolean + // in: query + // default: true + // description: when false, do not follow events // responses: // 200: // description: returns a string of json data describing an event diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index c885dc81a..83584d0f3 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -8,6 +8,10 @@ import ( "github.com/gorilla/mux" ) +// TODO +// +// * /images/create is missing the "message" and "platform" parameters + func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // swagger:operation POST /images/create compat createImage // --- @@ -631,13 +635,14 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // required: true // description: Name of image to push. // - in: query - // name: tag + // name: destination // type: string - // description: The tag to associate with the image on the registry. + // description: Allows for pushing the image to a different destintation than the image refers to. // - in: query - // name: credentials - // description: username:password for the registry. - // type: string + // name: tlsVerify + // description: Require TLS verification. + // type: boolean + // default: true // - in: header // name: X-Registry-Auth // type: string diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index b1189c1f4..2c60b2b27 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -3,11 +3,96 @@ package server import ( "net/http" + "github.com/containers/libpod/pkg/api/handlers/compat" "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/gorilla/mux" ) func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { + // swagger:operation DELETE /networks/{name} compat compatRemoveNetwork + // --- + // tags: + // - networks (compat) + // summary: Remove a network + // description: Remove a network + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name of the network + // produces: + // - application/json + // responses: + // 204: + // description: no error + // 404: + // $ref: "#/responses/NoSuchNetwork" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/networks/{name}"), s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete) + r.HandleFunc("/networks/{name}", s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete) + // swagger:operation GET /networks/{name}/json compat compatInspectNetwork + // --- + // tags: + // - networks (compat) + // summary: Inspect a network + // description: Display low level configuration network + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name of the network + // produces: + // - application/json + // responses: + // 200: + // $ref: "#/responses/CompatNetworkInspect" + // 404: + // $ref: "#/responses/NoSuchNetwork" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/networks/{name}/json"), s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet) + r.HandleFunc("/networks/{name}/json", s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet) + // swagger:operation GET /networks/json compat compatListNetwork + // --- + // tags: + // - networks (compat) + // summary: List networks + // description: Display summary of network configurations + // produces: + // - application/json + // responses: + // 200: + // $ref: "#/responses/CompatNetworkList" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/networks/json"), s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet) + r.HandleFunc("/networks", s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet) + // swagger:operation POST /networks/create compat compatCreateNetwork + // --- + // tags: + // - networks (compat) + // summary: Create network + // description: Create a network configuration + // produces: + // - application/json + // parameters: + // - in: body + // name: create + // description: attributes for creating a container + // schema: + // $ref: "#/definitions/NetworkCreateRequest" + // responses: + // 200: + // $ref: "#/responses/CompatNetworkCreate" + // 400: + // $ref: "#/responses/BadParamError" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/networks/create"), s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost) + r.HandleFunc("/networks/create", s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost) // swagger:operation DELETE /libpod/networks/{name} libpod libpodRemoveNetwork // --- // tags: @@ -33,6 +118,11 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchNetwork" // 500: // $ref: "#/responses/InternalError" + + /* + Libpod + */ + r.HandleFunc(VersionedPath("/libpod/networks/{name}"), s.APIHandler(libpod.RemoveNetwork)).Methods(http.MethodDelete) // swagger:operation GET /libpod/networks/{name}/json libpod libpodInspectNetwork // --- diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index d39528f45..499a4c58a 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -92,8 +92,17 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li }, ) + router.MethodNotAllowedHandler = http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // We can track user errors... + logrus.Infof("Failed Request: (%d:%s) for %s:'%s'", http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed), r.Method, r.URL.String()) + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + }, + ) + for _, fn := range []func(*mux.Router) error{ server.registerAuthHandlers, + server.registerAchiveHandlers, server.registerContainersHandlers, server.registerDistributionHandlers, server.registerEventsHandlers, diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index ebd99ba27..c463f809e 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -139,6 +139,13 @@ type swagImageSummary struct { Body []entities.ImageSummary } +// Registries summary +// swagger:response DocsRegistriesList +type swagRegistriesList struct { + // in:body + Body entities.ListRegistriesReport +} + // List Containers // swagger:response DocsListContainer type swagListContainers struct { diff --git a/pkg/api/tags.yaml b/pkg/api/tags.yaml index 1ffb5e940..f86f8dbea 100644 --- a/pkg/api/tags.yaml +++ b/pkg/api/tags.yaml @@ -21,5 +21,7 @@ tags: description: Actions related to exec for the compatibility endpoints - name: images (compat) description: Actions related to images for the compatibility endpoints + - name: networks (compat) + description: Actions related to compatibility networks - name: system (compat) description: Actions related to Podman and compatibility engines diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 000000000..ffa65f7e5 --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,216 @@ +package auth + +import ( + "encoding/base64" + "encoding/json" + "io/ioutil" + "net/http" + "os" + "strings" + + imageAuth "github.com/containers/image/v5/pkg/docker/config" + "github.com/containers/image/v5/types" + dockerAPITypes "github.com/docker/docker/api/types" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// XRegistryAuthHeader is the key to the encoded registry authentication +// configuration in an http-request header. +const XRegistryAuthHeader = "X-Registry-Auth" + +// GetCredentials extracts one or more DockerAuthConfigs from the request's +// header. The header could specify a single-auth config in which case the +// first return value is set. In case of a multi-auth header, the contents are +// stored in a temporary auth file (2nd return value). Note that the auth file +// should be removed after usage. +func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) { + authHeader := r.Header.Get(XRegistryAuthHeader) + if len(authHeader) == 0 { + return nil, "", nil + } + + // First look for a multi-auth header (i.e., a map). + authConfigs, err := multiAuthHeader(r) + if err == nil { + authfile, err := authConfigsToAuthFile(authConfigs) + return nil, authfile, err + } + + // Fallback to looking for a single-auth header (i.e., one config). + authConfigs, err = singleAuthHeader(r) + if err != nil { + return nil, "", err + } + var conf *types.DockerAuthConfig + for k := range authConfigs { + c := authConfigs[k] + conf = &c + break + } + return conf, "", nil +} + +// Header returns a map with the XRegistryAuthHeader set which can +// conveniently be used in the http stack. +func Header(sys *types.SystemContext, authfile, username, password string) (map[string]string, error) { + var content string + var err error + + if username != "" { + content, err = encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password}) + if err != nil { + return nil, err + } + } else { + if sys == nil { + sys = &types.SystemContext{} + } + if authfile != "" { + sys.AuthFilePath = authfile + } + authConfigs, err := imageAuth.GetAllCredentials(sys) + if err != nil { + return nil, err + } + content, err = encodeMultiAuthConfigs(authConfigs) + if err != nil { + return nil, err + } + } + + header := make(map[string]string) + header[XRegistryAuthHeader] = content + + return header, nil +} + +// RemoveAuthfile is a convenience function that is meant to be called in a +// deferred statement. If non-empty, it removes the specified authfile and log +// errors. It's meant to reduce boilerplate code at call sites of +// `GetCredentials`. +func RemoveAuthfile(authfile string) { + if authfile == "" { + return + } + if err := os.Remove(authfile); err != nil { + logrus.Errorf("Error removing temporary auth file %q: %v", authfile, err) + } +} + +// encodeSingleAuthConfig serializes the auth configuration as a base64 encoded JSON payload. +func encodeSingleAuthConfig(authConfig types.DockerAuthConfig) (string, error) { + conf := imageAuthToDockerAuth(authConfig) + buf, err := json.Marshal(conf) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buf), nil +} + +// encodeMultiAuthConfigs serializes the auth configurations as a base64 encoded JSON payload. +func encodeMultiAuthConfigs(authConfigs map[string]types.DockerAuthConfig) (string, error) { + confs := make(map[string]dockerAPITypes.AuthConfig) + for registry, authConf := range authConfigs { + confs[registry] = imageAuthToDockerAuth(authConf) + } + buf, err := json.Marshal(confs) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buf), nil +} + +// authConfigsToAuthFile stores the specified auth configs in a temporary files +// and returns its path. The file can later be used an auth file for contacting +// one or more container registries. If tmpDir is empty, the system's default +// TMPDIR will be used. +func authConfigsToAuthFile(authConfigs map[string]types.DockerAuthConfig) (string, error) { + // Intitialize an empty temporary JSON file. + tmpFile, err := ioutil.TempFile("", "auth.json.") + if err != nil { + return "", err + } + if _, err := tmpFile.Write([]byte{'{', '}'}); err != nil { + return "", errors.Wrap(err, "error initializing temporary auth file") + } + if err := tmpFile.Close(); err != nil { + return "", errors.Wrap(err, "error closing temporary auth file") + } + authFilePath := tmpFile.Name() + + // TODO: It would be nice if c/image could dump the map at once. + // + // Now use the c/image packages to store the credentials. It's battle + // tested, and we make sure to use the same code as the image backend. + sys := types.SystemContext{AuthFilePath: authFilePath} + for server, config := range authConfigs { + // Note that we do not validate the credentials here. Wassume + // that all credentials are valid. They'll be used on demand + // later. + if err := imageAuth.SetAuthentication(&sys, server, config.Username, config.Password); err != nil { + return "", errors.Wrapf(err, "error storing credentials in temporary auth file (server: %q, user: %q)", server, config.Username) + } + } + + return authFilePath, nil +} + +// dockerAuthToImageAuth converts a docker auth config to one we're using +// internally from c/image. Note that the Docker types look slightly +// different, so we need to convert to be extra sure we're not running into +// undesired side-effects when unmarhalling directly to our types. +func dockerAuthToImageAuth(authConfig dockerAPITypes.AuthConfig) types.DockerAuthConfig { + return types.DockerAuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + IdentityToken: authConfig.IdentityToken, + } +} + +// reverse conversion of `dockerAuthToImageAuth`. +func imageAuthToDockerAuth(authConfig types.DockerAuthConfig) dockerAPITypes.AuthConfig { + return dockerAPITypes.AuthConfig{ + Username: authConfig.Username, + Password: authConfig.Password, + IdentityToken: authConfig.IdentityToken, + } +} + +// singleAuthHeader extracts a DockerAuthConfig from the request's header. +// The header content is a single DockerAuthConfig. +func singleAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) { + authHeader := r.Header.Get(XRegistryAuthHeader) + authConfig := dockerAPITypes.AuthConfig{} + if len(authHeader) > 0 { + authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader)) + if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil { + return nil, err + } + } + authConfigs := make(map[string]types.DockerAuthConfig) + authConfigs["0"] = dockerAuthToImageAuth(authConfig) + return authConfigs, nil +} + +// multiAuthHeader extracts a DockerAuthConfig from the request's header. +// The header content is a map[string]DockerAuthConfigs. +func multiAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) { + authHeader := r.Header.Get(XRegistryAuthHeader) + if len(authHeader) == 0 { + return nil, nil + } + + dockerAuthConfigs := make(map[string]dockerAPITypes.AuthConfig) + authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader)) + if err := json.NewDecoder(authJSON).Decode(&dockerAuthConfigs); err != nil { + return nil, err + } + + // Now convert to the internal types. + authConfigs := make(map[string]types.DockerAuthConfig) + for server := range dockerAuthConfigs { + authConfigs[server] = dockerAuthToImageAuth(dockerAuthConfigs[server]) + } + return authConfigs, nil +} diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index d21d55beb..e9032f083 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -16,7 +16,6 @@ import ( "time" "github.com/blang/semver" - "github.com/containers/libpod/pkg/api/types" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -28,7 +27,7 @@ var ( basePath = &url.URL{ Scheme: "http", Host: "d", - Path: "/v" + types.MinimalAPIVersion + "/libpod", + Path: "/v" + APIVersion.String() + "/libpod", } ) @@ -151,23 +150,28 @@ func pingNewConnection(ctx context.Context) error { return err } // the ping endpoint sits at / in this case - response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil) + response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil, nil) if err != nil { return err } if response.StatusCode == http.StatusOK { - v, err := semver.ParseTolerant(response.Header.Get("Libpod-API-Version")) + versionHdr := response.Header.Get("Libpod-API-Version") + if versionHdr == "" { + logrus.Info("Service did not provide Libpod-API-Version Header") + return nil + } + versionSrv, err := semver.ParseTolerant(versionHdr) if err != nil { return err } - switch APIVersion.Compare(v) { + switch APIVersion.Compare(versionSrv) { case 1, 0: // Server's job when client version is equal or older return nil case -1: - return errors.Errorf("server API version is too old. client %q server %q", APIVersion.String(), v.String()) + return errors.Errorf("server API version is too old. client %q server %q", APIVersion.String(), versionSrv.String()) } } return errors.Errorf("ping response was %q", response.StatusCode) @@ -246,7 +250,7 @@ func unixClient(_url *url.URL) (Connection, error) { } // DoRequest assembles the http request and returns the response -func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, pathValues ...string) (*APIResponse, error) { +func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) { var ( err error response *http.Response @@ -267,6 +271,9 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, if len(queryParams) > 0 { req.URL.RawQuery = queryParams.Encode() } + for key, val := range header { + req.Header.Set(key, val) + } req = req.WithContext(context.WithValue(context.Background(), clientKey, c)) // Give the Do three chances in the case of a comm/service hiccup for i := 0; i < 3; i++ { diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go index 84924587b..f483a9297 100644 --- a/pkg/bindings/containers/checkpoint.go +++ b/pkg/bindings/containers/checkpoint.go @@ -34,7 +34,7 @@ func Checkpoint(ctx context.Context, nameOrId string, keep, leaveRunning, tcpEst if export != nil { params.Set("export", *export) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nameOrId) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrId) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func Restore(ctx context.Context, nameOrId string, keep, tcpEstablished, ignoreR if importArchive != nil { params.Set("import", *importArchive) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nameOrId) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrId) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/commit.go b/pkg/bindings/containers/commit.go index 12c25f842..780d42272 100644 --- a/pkg/bindings/containers/commit.go +++ b/pkg/bindings/containers/commit.go @@ -41,7 +41,7 @@ func Commit(ctx context.Context, nameOrId string, options CommitOptions) (handle if options.Tag != nil { params.Set("tag", *options.Tag) } - response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params) + response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params, nil) if err != nil { return id, err } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 39a077f36..516f3d282 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "os/signal" + "reflect" "strconv" "strings" @@ -60,7 +61,7 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int } params.Set("filters", filterString) } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params, nil) if err != nil { return containers, err } @@ -85,7 +86,7 @@ func Prune(ctx context.Context, filters map[string][]string) (*entities.Containe } params.Set("filters", filterString) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params, nil) if err != nil { return nil, err } @@ -107,7 +108,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error { if volumes != nil { params.Set("vols", strconv.FormatBool(*volumes)) } - response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID) if err != nil { return err } @@ -127,7 +128,7 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*define.InspectC if size != nil { params.Set("size", strconv.FormatBool(*size)) } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID) if err != nil { return nil, err } @@ -145,7 +146,7 @@ func Kill(ctx context.Context, nameOrID string, sig string) error { } params := url.Values{} params.Set("signal", sig) - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID) if err != nil { return err } @@ -160,7 +161,7 @@ func Pause(ctx context.Context, nameOrID string) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID) if err != nil { return err } @@ -179,7 +180,7 @@ func Restart(ctx context.Context, nameOrID string, timeout *int) error { if timeout != nil { params.Set("t", strconv.Itoa(*timeout)) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID) if err != nil { return err } @@ -198,7 +199,7 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error { if detachKeys != nil { params.Set("detachKeys", *detachKeys) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID) if err != nil { return err } @@ -220,7 +221,7 @@ func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string, // flatten the slice into one string params.Set("ps_args", strings.Join(descriptors, ",")) } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID) if err != nil { return nil, err } @@ -248,7 +249,7 @@ func Unpause(ctx context.Context, nameOrID string) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID) if err != nil { return err } @@ -268,7 +269,7 @@ func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatu if condition != nil { params.Set("condition", condition.String()) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID) if err != nil { return exitCode, err } @@ -283,7 +284,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nil, nameOrID) if err != nil { return false, err } @@ -301,7 +302,7 @@ func Stop(ctx context.Context, nameOrID string, timeout *uint) error { if timeout != nil { params.Set("t", strconv.Itoa(int(*timeout))) } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID) if err != nil { return err } @@ -316,7 +317,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID) if err != nil { return err } @@ -335,7 +336,7 @@ func ContainerInit(ctx context.Context, nameOrID string) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID) if err != nil { return err } @@ -347,6 +348,26 @@ func ContainerInit(ctx context.Context, nameOrID string) error { // Attach attaches to a running container func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stream *bool, stdin io.Reader, stdout io.Writer, stderr io.Writer, attachReady chan bool) error { + isSet := struct { + stdin bool + stdout bool + stderr bool + }{ + stdin: !(stdin == nil || reflect.ValueOf(stdin).IsNil()), + stdout: !(stdout == nil || reflect.ValueOf(stdout).IsNil()), + stderr: !(stderr == nil || reflect.ValueOf(stderr).IsNil()), + } + // Ensure golang can determine that interfaces are "really" nil + if !isSet.stdin { + stdin = (io.Reader)(nil) + } + if !isSet.stdout { + stdout = (io.Writer)(nil) + } + if !isSet.stderr { + stderr = (io.Writer)(nil) + } + conn, err := bindings.GetClient(ctx) if err != nil { return err @@ -368,13 +389,13 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre if stream != nil { params.Add("stream", fmt.Sprintf("%t", *stream)) } - if stdin != nil { + if isSet.stdin { params.Add("stdin", "true") } - if stdout != nil { + if isSet.stdout { params.Add("stdout", "true") } - if stderr != nil { + if isSet.stderr { params.Add("stderr", "true") } @@ -422,32 +443,26 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre }() } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/attach", params, nameOrId) + response, err := conn.DoRequest(stdin, http.MethodPost, "/containers/%s/attach", params, nil, nameOrId) if err != nil { return err } - defer response.Body.Close() + if !(response.IsSuccess() || response.IsInformational()) { + return response.Process(nil) + } + // If we are attaching around a start, we need to "signal" // back that we are in fact attached so that started does // not execute before we can attach. if attachReady != nil { attachReady <- true } - if !(response.IsSuccess() || response.IsInformational()) { - return response.Process(nil) - } - - if stdin != nil { - go func() { - _, err := io.Copy(conn, stdin) - if err != nil { - logrus.Error("failed to write input to service: " + err.Error()) - } - }() - } buffer := make([]byte, 1024) if ctnr.Config.Tty { + if !isSet.stdout { + return fmt.Errorf("container %q requires stdout to be set", ctnr.ID) + } // If not multiplex'ed, read from server and write to stdout _, err := io.Copy(stdout, response.Body) if err != nil { @@ -469,25 +484,25 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre } switch { - case fd == 0 && stdin != nil: + case fd == 0 && isSet.stdout: _, err := stdout.Write(frame[0:l]) if err != nil { return err } - case fd == 1 && stdout != nil: + case fd == 1 && isSet.stdout: _, err := stdout.Write(frame[0:l]) if err != nil { return err } - case fd == 2 && stderr != nil: + case fd == 2 && isSet.stderr: _, err := stderr.Write(frame[0:l]) if err != nil { return err } case fd == 3: - return errors.New("error from service in stream: " + string(frame)) + return fmt.Errorf("error from service from stream: %s", frame) default: - return fmt.Errorf("unrecognized input header: %d", fd) + return fmt.Errorf("unrecognized channel in header: %d, 0-3 supported", fd) } } } @@ -520,6 +535,7 @@ func DemuxFrame(r io.Reader, buffer []byte, length int) (frame []byte, err error if len(buffer) < length { buffer = append(buffer, make([]byte, length-len(buffer)+1)...) } + n, err := io.ReadFull(r, buffer[0:length]) if err != nil { return nil, nil @@ -528,6 +544,7 @@ func DemuxFrame(r io.Reader, buffer []byte, length int) (frame []byte, err error err = io.ErrUnexpectedEOF return } + return buffer[0:length], nil } @@ -555,7 +572,7 @@ func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) er if width != nil { params.Set("w", strconv.Itoa(*width)) } - rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params) + rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params, nil) if err != nil { return err } diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go index 21355f24b..4603b8653 100644 --- a/pkg/bindings/containers/create.go +++ b/pkg/bindings/containers/create.go @@ -22,7 +22,7 @@ func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator) (entities.Con return ccr, err } stringReader := strings.NewReader(specgenString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil, nil) if err != nil { return ccr, err } diff --git a/pkg/bindings/containers/diff.go b/pkg/bindings/containers/diff.go index 82070ca9a..06a828c30 100644 --- a/pkg/bindings/containers/diff.go +++ b/pkg/bindings/containers/diff.go @@ -15,7 +15,7 @@ func Diff(ctx context.Context, nameOrId string) ([]archive.Change, error) { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", nil, nameOrId) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", nil, nil, nameOrId) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go index 48f9ed697..2aeeae1f8 100644 --- a/pkg/bindings/containers/exec.go +++ b/pkg/bindings/containers/exec.go @@ -34,7 +34,7 @@ func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreat } jsonReader := strings.NewReader(string(requestJSON)) - resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID) + resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID) if err != nil { return "", err } @@ -57,7 +57,7 @@ func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSess logrus.Debugf("Inspecting session ID %q", sessionID) - resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID) + resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go index 2b783ac73..b726acf49 100644 --- a/pkg/bindings/containers/healthcheck.go +++ b/pkg/bindings/containers/healthcheck.go @@ -18,7 +18,7 @@ func RunHealthCheck(ctx context.Context, nameOrID string) (*define.HealthCheckRe var ( status define.HealthCheckResults ) - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go index 20c8b4292..bec4ebb3c 100644 --- a/pkg/bindings/containers/logs.go +++ b/pkg/bindings/containers/logs.go @@ -46,11 +46,10 @@ func Logs(ctx context.Context, nameOrID string, opts LogOptions, stdoutChan, std if opts.Stdout == nil && opts.Stderr == nil { params.Set("stdout", strconv.FormatBool(true)) } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID) if err != nil { return err } - defer response.Body.Close() buffer := make([]byte, 1024) for { diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go index e0627d9a3..2d553142f 100644 --- a/pkg/bindings/containers/mount.go +++ b/pkg/bindings/containers/mount.go @@ -17,7 +17,7 @@ func Mount(ctx context.Context, nameOrID string) (string, error) { var ( path string ) - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID) if err != nil { return path, err } @@ -31,7 +31,7 @@ func Unmount(ctx context.Context, nameOrID string) error { if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID) if err != nil { return err } @@ -45,7 +45,7 @@ func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) { return nil, err } mounts := make(map[string]string) - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil, nil) if err != nil { return mounts, err } diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go index d3177133f..161b722f3 100644 --- a/pkg/bindings/generate/generate.go +++ b/pkg/bindings/generate/generate.go @@ -18,7 +18,7 @@ func GenerateKube(ctx context.Context, nameOrID string, options entities.Generat params := url.Values{} params.Set("service", strconv.FormatBool(options.Service)) - response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/images/diff.go b/pkg/bindings/images/diff.go index cfdd06a97..e2d344ea0 100644 --- a/pkg/bindings/images/diff.go +++ b/pkg/bindings/images/diff.go @@ -15,7 +15,7 @@ func Diff(ctx context.Context, nameOrId string) ([]archive.Change, error) { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nameOrId) + response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrId) if err != nil { return nil, err } diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index f9c02d199..e0802a6e1 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -12,6 +12,7 @@ import ( "github.com/containers/buildah" "github.com/containers/image/v5/types" "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/auth" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" "github.com/docker/go-units" @@ -26,7 +27,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID) if err != nil { return false, err } @@ -52,7 +53,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit } params.Set("filters", strFilters) } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params) + response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params, nil) if err != nil { return imageSummary, err } @@ -71,7 +72,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image params.Set("size", strconv.FormatBool(*size)) } inspectedData := entities.ImageInspectReport{} - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID) if err != nil { return &inspectedData, err } @@ -89,7 +90,7 @@ func Tree(ctx context.Context, nameOrId string, whatRequires *bool) (*entities.I if whatRequires != nil { params.Set("size", strconv.FormatBool(*whatRequires)) } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/tree", params, nameOrId) + response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrId) if err != nil { return nil, err } @@ -103,7 +104,7 @@ func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID) if err != nil { return history, err } @@ -120,7 +121,7 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe if name != nil { params.Set("reference", *name) } - response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params) + response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params, nil) if err != nil { return nil, err } @@ -141,7 +142,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c if compress != nil { params.Set("compress", strconv.FormatBool(*compress)) } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nil, nameOrID) if err != nil { return err } @@ -174,7 +175,7 @@ func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]strin } params.Set("filters", stringFilter) } - response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params) + response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params, nil) if err != nil { return deleted, err } @@ -190,7 +191,7 @@ func Tag(ctx context.Context, nameOrID, tag, repo string) error { params := url.Values{} params.Set("tag", tag) params.Set("repo", repo) - response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID) if err != nil { return err } @@ -206,7 +207,7 @@ func Untag(ctx context.Context, nameOrID, tag, repo string) error { params := url.Values{} params.Set("tag", tag) params.Set("repo", repo) - response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID) if err != nil { return err } @@ -297,7 +298,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } // TODO outputs? - response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params) + response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params, nil) if err != nil { return nil, err } @@ -341,7 +342,7 @@ func Import(ctx context.Context, changes []string, message, reference, u *string if u != nil { params.Set("url", *u) } - response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params) + response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params, nil) if err != nil { return nil, err } @@ -359,7 +360,6 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption } params := url.Values{} params.Set("reference", rawImage) - params.Set("credentials", options.Credentials) params.Set("overrideArch", options.OverrideArch) params.Set("overrideOS", options.OverrideOS) if options.SkipTLSVerify != types.OptionalBoolUndefined { @@ -369,7 +369,13 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption } params.Set("allTags", strconv.FormatBool(options.AllTags)) - response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params) + // TODO: have a global system context we can pass around (1st argument) + header, err := auth.Header(nil, options.Authfile, options.Username, options.Password) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params, header) if err != nil { return nil, err } @@ -397,8 +403,14 @@ func Push(ctx context.Context, source string, destination string, options entiti if err != nil { return err } + + // TODO: have a global system context we can pass around (1st argument) + header, err := auth.Header(nil, options.Authfile, options.Username, options.Password) + if err != nil { + return err + } + params := url.Values{} - params.Set("credentials", options.Credentials) params.Set("destination", destination) if options.SkipTLSVerify != types.OptionalBoolUndefined { // Note: we have to verify if skipped is false. @@ -407,8 +419,12 @@ func Push(ctx context.Context, source string, destination string, options entiti } path := fmt.Sprintf("/images/%s/push", source) - _, err = conn.DoRequest(nil, http.MethodPost, path, params) - return err + response, err := conn.DoRequest(nil, http.MethodPost, path, params, header) + if err != nil { + return err + } + + return response.Process(err) } // Search is the binding for libpod's v2 endpoints for Search images. @@ -430,7 +446,13 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions) params.Set("tlsVerify", strconv.FormatBool(verifyTLS)) } - response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params) + // TODO: have a global system context we can pass around (1st argument) + header, err := auth.Header(nil, opts.Authfile, "", "") + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params, header) if err != nil { return nil, err } diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go index 05aa3f9ca..c315bfce7 100644 --- a/pkg/bindings/images/rm.go +++ b/pkg/bindings/images/rm.go @@ -30,7 +30,7 @@ func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemove params.Add("images", i) } - response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params) + response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params, nil) if err != nil { return nil, []error{err} } @@ -52,7 +52,7 @@ func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRe params := url.Values{} params.Set("force", strconv.FormatBool(force)) - response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID) if err != nil { return nil, err } diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index f5ee31d93..e89624667 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -39,7 +39,7 @@ func Create(ctx context.Context, names, images []string, all *bool) (string, err params.Add("image", i) } - response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params) + response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params, nil) if err != nil { return "", err } @@ -53,7 +53,7 @@ func Inspect(ctx context.Context, name string) (*manifest.Schema2List, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, name) + response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, nil, name) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func Add(ctx context.Context, name string, options image.ManifestAddOpts) (strin return "", err } stringReader := strings.NewReader(optionsString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, name) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name) if err != nil { return "", err } @@ -90,7 +90,7 @@ func Remove(ctx context.Context, name, digest string) (string, error) { } params := url.Values{} params.Set("digest", digest) - response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, name) + response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, nil, name) if err != nil { return "", err } @@ -118,7 +118,7 @@ func Push(ctx context.Context, name string, destination *string, all *bool) (str if all != nil { params.Set("all", strconv.FormatBool(*all)) } - _, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, name) + _, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name) if err != nil { return "", err } diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 7bba4f478..34881b524 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -28,7 +28,7 @@ func Create(ctx context.Context, options entities.NetworkCreateOptions, name *st return nil, err } stringReader := strings.NewReader(networkConfig) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/create", params) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/create", params, nil) if err != nil { return nil, err } @@ -42,7 +42,7 @@ func Inspect(ctx context.Context, nameOrID string) ([]entities.NetworkInspectRep if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID) if err != nil { return nil, err } @@ -62,7 +62,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) ([]*entities.Netw if force != nil { params.Set("size", strconv.FormatBool(*force)) } - response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID) if err != nil { return nil, err } @@ -78,7 +78,7 @@ func List(ctx context.Context) ([]*entities.NetworkListReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil) + response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil, nil) if err != nil { return netList, err } diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go index 653558a3c..288cca454 100644 --- a/pkg/bindings/play/play.go +++ b/pkg/bindings/play/play.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/containers/image/v5/types" + "github.com/containers/libpod/pkg/auth" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" ) @@ -31,7 +32,13 @@ func PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions params.Set("tlsVerify", strconv.FormatBool(options.SkipTLSVerify == types.OptionalBoolTrue)) } - response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params) + // TODO: have a global system context we can pass around (1st argument) + header, err := auth.Header(nil, options.Authfile, options.Username, options.Password) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params, header) if err != nil { return nil, err } diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index b213c8c73..fb273fdf3 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -28,7 +28,7 @@ func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entit return nil, err } stringReader := strings.NewReader(specgenString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil, nil) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID) if err != nil { return false, err } @@ -57,7 +57,7 @@ func Inspect(ctx context.Context, nameOrID string) (*entities.PodInspectReport, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID) if err != nil { return nil, err } @@ -78,7 +78,7 @@ func Kill(ctx context.Context, nameOrID string, signal *string) (*entities.PodKi if signal != nil { params.Set("signal", *signal) } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID) if err != nil { return nil, err } @@ -92,7 +92,7 @@ func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, erro if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID) if err != nil { return nil, err } @@ -107,7 +107,7 @@ func Prune(ctx context.Context) ([]*entities.PodPruneReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil, nil) if err != nil { return nil, err } @@ -132,7 +132,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.ListPod } params.Set("filters", stringFilter) } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params) + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params, nil) if err != nil { return podsReports, err } @@ -146,7 +146,7 @@ func Restart(ctx context.Context, nameOrID string) (*entities.PodRestartReport, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID) if err != nil { return nil, err } @@ -165,7 +165,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) (*entities.PodRmR if force != nil { params.Set("force", strconv.FormatBool(*force)) } - response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID) if err != nil { return nil, err } @@ -179,7 +179,7 @@ func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, erro if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID) if err != nil { return nil, err } @@ -202,7 +202,7 @@ func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStop if timeout != nil { params.Set("t", strconv.Itoa(*timeout)) } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID) if err != nil { return nil, err } @@ -226,7 +226,7 @@ func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string, // flatten the slice into one string params.Set("ps_args", strings.Join(descriptors, ",")) } - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID) if err != nil { return nil, err } @@ -254,7 +254,7 @@ func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport, if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID) if err != nil { return nil, err } @@ -277,7 +277,7 @@ func Stats(ctx context.Context, namesOrIDs []string, options entities.PodStatsOp params.Set("all", strconv.FormatBool(options.All)) var reports []*entities.PodStatsReport - response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params) + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/system/info.go b/pkg/bindings/system/info.go index 13e12645d..8ad704f84 100644 --- a/pkg/bindings/system/info.go +++ b/pkg/bindings/system/info.go @@ -15,7 +15,7 @@ func Info(ctx context.Context) (*define.Info, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil) + response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go index 5348d0cfb..010762bef 100644 --- a/pkg/bindings/system/system.go +++ b/pkg/bindings/system/system.go @@ -20,7 +20,7 @@ import ( // Events allows you to monitor libdpod related events like container creation and // removal. The events are then passed to the eventChan provided. The optional cancelChan // can be used to cancel the read of events and close down the HTTP connection. -func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error { +func Events(ctx context.Context, eventChan chan entities.Event, cancelChan chan bool, since, until *string, filters map[string][]string, stream *bool) error { conn, err := bindings.GetClient(ctx) if err != nil { return err @@ -32,6 +32,9 @@ func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan cha if until != nil { params.Set("until", *until) } + if stream != nil { + params.Set("stream", strconv.FormatBool(*stream)) + } if filters != nil { filterString, err := bindings.FiltersToString(filters) if err != nil { @@ -39,7 +42,7 @@ func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan cha } params.Set("filters", filterString) } - response, err := conn.DoRequest(nil, http.MethodGet, "/events", params) + response, err := conn.DoRequest(nil, http.MethodGet, "/events", params, nil) if err != nil { return err } @@ -50,18 +53,24 @@ func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan cha logrus.Error(errors.Wrap(err, "unable to close event response body")) }() } + dec := json.NewDecoder(response.Body) - for { - e := entities.Event{} - if err := dec.Decode(&e); err != nil { - if err == io.EOF { - break - } - return errors.Wrap(err, "unable to decode event response") + for err = (error)(nil); err == nil; { + var e = entities.Event{} + err = dec.Decode(&e) + if err == nil { + eventChan <- e } - eventChan <- e } - return nil + close(eventChan) + switch { + case err == nil: + return nil + case errors.Is(err, io.EOF): + return nil + default: + return errors.Wrap(err, "unable to decode event response") + } } // Prune removes all unused system data. @@ -80,7 +89,7 @@ func Prune(ctx context.Context, all, volumes *bool) (*entities.SystemPruneReport if volumes != nil { params.Set("Volumes", strconv.FormatBool(*volumes)) } - response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params) + response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params, nil) if err != nil { return nil, err } @@ -101,7 +110,7 @@ func Version(ctx context.Context) (*entities.SystemVersionReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/version", nil) + response, err := conn.DoRequest(nil, http.MethodGet, "/version", nil, nil) if err != nil { return nil, err } @@ -130,7 +139,7 @@ func DiskUsage(ctx context.Context) (*entities.SystemDfReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/system/df", nil) + response, err := conn.DoRequest(nil, http.MethodGet, "/system/df", nil, nil) if err != nil { return nil, err } diff --git a/pkg/bindings/test/auth_test.go b/pkg/bindings/test/auth_test.go new file mode 100644 index 000000000..fdb190551 --- /dev/null +++ b/pkg/bindings/test/auth_test.go @@ -0,0 +1,143 @@ +package test_bindings + +import ( + "io/ioutil" + "os" + "time" + + "github.com/containers/common/pkg/auth" + "github.com/containers/image/v5/types" + podmanRegistry "github.com/containers/libpod/hack/podman-registry-go" + "github.com/containers/libpod/pkg/bindings/images" + "github.com/containers/libpod/pkg/domain/entities" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman images", func() { + var ( + registry *podmanRegistry.Registry + bt *bindingTest + s *gexec.Session + err error + ) + + BeforeEach(func() { + // Note: we need to start the registry **before** setting up + // the test. Otherwise, the registry is not reachable for + // currently unknown reasons. + registry, err = podmanRegistry.Start() + Expect(err).To(BeNil()) + + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + err := bt.NewConnection() + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + registry.Stop() + }) + + // Test using credentials. + It("tag + push + pull (with credentials)", func() { + + imageRep := "localhost:" + registry.Port + "/test" + imageTag := "latest" + imageRef := imageRep + ":" + imageTag + + // Tag the alpine image and verify it has worked. + err = images.Tag(bt.conn, alpine.shortName, imageTag, imageRep) + Expect(err).To(BeNil()) + _, err = images.GetImage(bt.conn, imageRef, nil) + Expect(err).To(BeNil()) + + // Now push the image. + pushOpts := entities.ImagePushOptions{ + Username: registry.User, + Password: registry.Password, + SkipTLSVerify: types.OptionalBoolTrue, + } + err = images.Push(bt.conn, imageRef, imageRef, pushOpts) + Expect(err).To(BeNil()) + + // Now pull the image. + pullOpts := entities.ImagePullOptions{ + Username: registry.User, + Password: registry.Password, + SkipTLSVerify: types.OptionalBoolTrue, + } + _, err = images.Pull(bt.conn, imageRef, pullOpts) + Expect(err).To(BeNil()) + }) + + // Test using authfile. + It("tag + push + pull + search (with authfile)", func() { + + imageRep := "localhost:" + registry.Port + "/test" + imageTag := "latest" + imageRef := imageRep + ":" + imageTag + + // Create a temporary authentication file. + tmpFile, err := ioutil.TempFile("", "auth.json.") + Expect(err).To(BeNil()) + _, err = tmpFile.Write([]byte{'{', '}'}) + Expect(err).To(BeNil()) + err = tmpFile.Close() + Expect(err).To(BeNil()) + + authFilePath := tmpFile.Name() + + // Now login to a) test the credentials and to b) store them in + // the authfile for later use. + sys := types.SystemContext{ + AuthFilePath: authFilePath, + DockerInsecureSkipTLSVerify: types.OptionalBoolTrue, + } + loginOptions := auth.LoginOptions{ + Username: registry.User, + Password: registry.Password, + AuthFile: authFilePath, + Stdin: os.Stdin, + Stdout: os.Stdout, + } + err = auth.Login(bt.conn, &sys, &loginOptions, []string{imageRep}) + Expect(err).To(BeNil()) + + // Tag the alpine image and verify it has worked. + err = images.Tag(bt.conn, alpine.shortName, imageTag, imageRep) + Expect(err).To(BeNil()) + _, err = images.GetImage(bt.conn, imageRef, nil) + Expect(err).To(BeNil()) + + // Now push the image. + pushOpts := entities.ImagePushOptions{ + Authfile: authFilePath, + SkipTLSVerify: types.OptionalBoolTrue, + } + err = images.Push(bt.conn, imageRef, imageRef, pushOpts) + Expect(err).To(BeNil()) + + // Now pull the image. + pullOpts := entities.ImagePullOptions{ + Authfile: authFilePath, + SkipTLSVerify: types.OptionalBoolTrue, + } + _, err = images.Pull(bt.conn, imageRef, pullOpts) + Expect(err).To(BeNil()) + + // Last, but not least, exercise search. + searchOptions := entities.ImageSearchOptions{ + Authfile: authFilePath, + SkipTLSVerify: types.OptionalBoolTrue, + } + _, err = images.Search(bt.conn, imageRef, searchOptions) + Expect(err).To(BeNil()) + }) + +}) diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 27ab2f555..dd3778754 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -47,13 +47,13 @@ var _ = Describe("Podman system", func() { } }() go func() { - system.Events(bt.conn, eChan, cancelChan, nil, nil, nil) + system.Events(bt.conn, eChan, cancelChan, nil, nil, nil, bindings.PFalse) }() _, err := bt.RunTopContainer(nil, nil, nil) Expect(err).To(BeNil()) cancelChan <- true - Expect(len(messages)).To(BeNumerically("==", 3)) + Expect(len(messages)).To(BeNumerically("==", 5)) }) It("podman system prune - pod,container stopped", func() { diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index cef9246cb..ebe19794a 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -26,7 +26,7 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions) (*entities return nil, err } stringReader := strings.NewReader(createString) - response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil, nil) if err != nil { return nil, err } @@ -42,7 +42,7 @@ func Inspect(ctx context.Context, nameOrID string) (*entities.VolumeConfigRespon if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID) if err != nil { return &inspect, err } @@ -67,7 +67,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeL } params.Set("filters", strFilters) } - response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params) + response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params, nil) if err != nil { return vols, err } @@ -83,7 +83,7 @@ func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil) + response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil, nil) if err != nil { return nil, err } @@ -101,7 +101,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error { if force != nil { params.Set("force", strconv.FormatBool(*force)) } - response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nameOrID) + response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID) if err != nil { return err } diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go index 265c9f36f..db58befa5 100644 --- a/pkg/domain/entities/engine.go +++ b/pkg/domain/entities/engine.go @@ -37,19 +37,20 @@ type PodmanConfig struct { *config.Config *pflag.FlagSet - CGroupUsage string // rootless code determines Usage message - ConmonPath string // --conmon flag will set Engine.ConmonPath - CpuProfile string // Hidden: Should CPU profile be taken - EngineMode EngineMode // ABI or Tunneling mode - Identities []string // ssh identities for connecting to server - MaxWorks int // maximum number of parallel threads - RuntimePath string // --runtime flag will set Engine.RuntimePath - SpanCloser io.Closer // Close() for tracing object - SpanCtx context.Context // context to use when tracing - Span opentracing.Span // tracing object - Syslog bool // write to StdOut and Syslog, not supported when tunneling - Trace bool // Hidden: Trace execution - Uri string // URI to API Service + CGroupUsage string // rootless code determines Usage message + ConmonPath string // --conmon flag will set Engine.ConmonPath + CpuProfile string // Hidden: Should CPU profile be taken + EngineMode EngineMode // ABI or Tunneling mode + Identities []string // ssh identities for connecting to server + MaxWorks int // maximum number of parallel threads + RegistriesConf string // allows for specifying a custom registries.conf + RuntimePath string // --runtime flag will set Engine.RuntimePath + SpanCloser io.Closer // Close() for tracing object + SpanCtx context.Context // context to use when tracing + Span opentracing.Span // tracing object + Syslog bool // write to StdOut and Syslog, not supported when tunneling + Trace bool // Hidden: Trace execution + Uri string // URI to API Service Runroot string StorageDriver string diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index edd217615..68a42d897 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -14,6 +14,12 @@ type GenerateSystemdOptions struct { RestartPolicy string // StopTimeout - time when stopping the container. StopTimeout *uint + // ContainerPrefix - systemd unit name prefix for containers + ContainerPrefix string + // PodPrefix - systemd unit name prefix for pods + PodPrefix string + // Separator - systemd unit name seperator between name/id and prefix + Separator string } // GenerateSystemdReport diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 0f909ab37..19a2c87f5 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -50,7 +50,7 @@ func (i *Image) Id() string { } type ImageSummary struct { - ID string + ID string `json:"Id"` ParentId string `json:",omitempty"` RepoTags []string `json:",omitempty"` Created time.Time `json:",omitempty"` @@ -128,9 +128,10 @@ type ImagePullOptions struct { // CertDir is the path to certificate directories. Ignored for remote // calls. CertDir string - // Credentials for authenticating against the registry in the format - // USERNAME:PASSWORD. - Credentials string + // Username for authenticating against the registry. + Username string + // Password for authenticating against the registry. + Password string // OverrideArch will overwrite the local architecture for image pulls. OverrideArch string // OverrideOS will overwrite the local operating system (OS) for image @@ -162,9 +163,10 @@ type ImagePushOptions struct { // transport. Default is same compression type as source. Ignored for remote // calls. Compress bool - // Credentials for authenticating against the registry in the format - // USERNAME:PASSWORD. - Credentials string + // Username for authenticating against the registry. + Username string + // Password for authenticating against the registry. + Password string // DigestFile, after copying the image, write the digest of the resulting // image to the file. Ignored for remote calls. DigestFile string diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 273052bb9..853619b19 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -1,5 +1,9 @@ package entities +import "github.com/containers/image/v5/types" + +// TODO: add comments to *all* types and fields. + type ManifestCreateOptions struct { All bool `schema:"all"` } @@ -26,6 +30,9 @@ type ManifestAnnotateOptions struct { } type ManifestPushOptions struct { - Purge, Quiet, All, TlsVerify, RemoveSignatures bool - Authfile, CertDir, Creds, DigestFile, Format, SignBy string + Purge, Quiet, All, RemoveSignatures bool + + Authfile, CertDir, Username, Password, DigestFile, Format, SignBy string + + SkipTLSVerify types.OptionalBool } diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index 93864c23b..4f485cbee 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -8,9 +8,10 @@ type PlayKubeOptions struct { Authfile string // CertDir - to a directory containing TLS certifications and keys. CertDir string - // Credentials - `username:password` for authentication against a - // container registry. - Credentials string + // Username for authenticating against the registry. + Username string + // Password for authenticating against the registry. + Password string // Network - name of the CNI network to connect to. Network string // Quiet - suppress output when pulling images. diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index a4896ce4d..1a38a7aa4 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -184,6 +184,8 @@ type PodInspectOptions struct { // Options for the API. NameOrID string + + Format string } type PodInspectReport struct { diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index 5e4760d12..79a90be48 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -97,3 +97,9 @@ type SystemVersionReport struct { type ComponentVersion struct { types.Version } + +// ListRegistriesReport is the report when querying for a sorted list of +// registries which may be contacted during certain operations. +type ListRegistriesReport struct { + Registries []string +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index b4e38ca23..e982c7c11 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1087,6 +1087,7 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) { } func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error { + defer close(options.StatChan) containerFunc := ic.Libpod.GetRunningContainers switch { case len(namesOrIds) > 0: diff --git a/pkg/domain/infra/abi/events.go b/pkg/domain/infra/abi/events.go index 20773cdce..7ec9db369 100644 --- a/pkg/domain/infra/abi/events.go +++ b/pkg/domain/infra/abi/events.go @@ -5,12 +5,9 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/domain/entities" - "github.com/sirupsen/logrus" ) func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptions) error { readOpts := events.ReadOptions{FromStart: opts.FromStart, Stream: opts.Stream, Filters: opts.Filter, EventChannel: opts.EventChan, Since: opts.Since, Until: opts.Until} - err := ic.Libpod.Events(readOpts) - logrus.Error(err) - return err + return ic.Libpod.Events(readOpts) } diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go index be5d452bd..abb5e2911 100644 --- a/pkg/domain/infra/abi/generate.go +++ b/pkg/domain/infra/abi/generate.go @@ -159,14 +159,14 @@ func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) { var kind, name, ctrName string if pod == nil { - kind = "container" + kind = options.ContainerPrefix //defaults to container name = ctr.ID() if options.Name { name = ctr.Name() } ctrName = name } else { - kind = "pod" + kind = options.PodPrefix //defaults to pod name = pod.ID() ctrName = ctr.ID() if options.Name { @@ -174,7 +174,7 @@ func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entitie ctrName = ctr.Name() } } - return ctrName, fmt.Sprintf("%s-%s", kind, name) + return ctrName, fmt.Sprintf("%s%s%s", kind, options.Separator, name) } func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 6e774df8e..d8af4d339 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -121,12 +121,11 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti } var registryCreds *types.DockerAuthConfig - if options.Credentials != "" { - creds, err := util.ParseRegistryCreds(options.Credentials) - if err != nil { - return nil, err + if len(options.Username) > 0 && len(options.Password) > 0 { + registryCreds = &types.DockerAuthConfig{ + Username: options.Username, + Password: options.Password, } - registryCreds = creds } dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, @@ -226,12 +225,11 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri } var registryCreds *types.DockerAuthConfig - if options.Credentials != "" { - creds, err := util.ParseRegistryCreds(options.Credentials) - if err != nil { - return err + if len(options.Username) > 0 && len(options.Password) > 0 { + registryCreds = &types.DockerAuthConfig{ + Username: options.Username, + Password: options.Password, } - registryCreds = creds } dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index fca34dda2..6e311dec7 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -16,6 +16,7 @@ import ( "github.com/containers/image/v5/docker" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" libpodImage "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" @@ -179,12 +180,28 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts en case "v2s2", "docker": manifestType = manifest.DockerV2Schema2MediaType default: - return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci' or 'v2s2'", opts.Format) + return errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format) } } + + // Set the system context. + sys := ir.Libpod.SystemContext() + if sys != nil { + sys = &types.SystemContext{} + } + sys.AuthFilePath = opts.Authfile + sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify + + if opts.Username != "" && opts.Password != "" { + sys.DockerAuthConfig = &types.DockerAuthConfig{ + Username: opts.Username, + Password: opts.Password, + } + } + options := manifests.PushOptions{ Store: ir.Libpod.GetStore(), - SystemContext: ir.Libpod.SystemContext(), + SystemContext: sys, ImageListSelection: cp.CopySpecificImages, Instances: nil, RemoveSignatures: opts.RemoveSignatures, diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index cd7eec7e6..6d0919d2b 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -56,6 +56,9 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en return nil, errors.Wrapf(err, "unable to read %q as YAML", path) } + // NOTE: pkg/bindings/play is also parsing the file. + // A pkg/kube would be nice to refactor and abstract + // parts of the K8s-related code. if podYAML.Kind != "Pod" { return nil, errors.Errorf("invalid YAML kind: %q. Pod is the only supported Kubernetes YAML kind", podYAML.Kind) } @@ -147,6 +150,13 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en writer = os.Stderr } + if len(options.Username) > 0 && len(options.Password) > 0 { + registryCreds = &types.DockerAuthConfig{ + Username: options.Username, + Password: options.Password, + } + } + dockerRegistryOptions := image.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, DockerCertPath: options.CertDir, diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index af2ec5f7b..52dfaba7d 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -16,56 +16,18 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" - iopodman "github.com/containers/libpod/pkg/varlink" - iopodmanAPI "github.com/containers/libpod/pkg/varlinkapi" "github.com/containers/libpod/utils" - "github.com/containers/libpod/version" "github.com/docker/distribution/reference" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/varlink/go/varlink" ) func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return ic.Libpod.Info() } -func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error { - var varlinkInterfaces = []*iopodman.VarlinkInterface{ - iopodmanAPI.New(opts.Command, ic.Libpod), - } - - service, err := varlink.NewService( - "Atomic", - "podman", - version.Version, - "https://github.com/containers/libpod", - ) - if err != nil { - return errors.Wrapf(err, "unable to create new varlink service") - } - - for _, i := range varlinkInterfaces { - if err := service.RegisterInterface(i); err != nil { - return errors.Errorf("unable to register varlink interface %v", i) - } - } - - // Run the varlink server at the given address - if err = service.Listen(opts.URI, opts.Timeout); err != nil { - switch err.(type) { - case varlink.ServiceTimeoutError: - logrus.Infof("varlink service expired (use --timeout to increase session time beyond %s ms, 0 means never timeout)", opts.Timeout.String()) - return nil - default: - return errors.Wrapf(err, "unable to start varlink service") - } - } - return nil -} - func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error { // do it only after podman has already re-execed and running with uid==0. if os.Geteuid() == 0 { diff --git a/pkg/domain/infra/abi/system_novalink.go b/pkg/domain/infra/abi/system_novalink.go new file mode 100644 index 000000000..a71b0170a --- /dev/null +++ b/pkg/domain/infra/abi/system_novalink.go @@ -0,0 +1,14 @@ +// +build !varlink + +package abi + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error { + return errors.Errorf("varlink is not supported") +} diff --git a/pkg/domain/infra/abi/system_varlink.go b/pkg/domain/infra/abi/system_varlink.go new file mode 100644 index 000000000..4dc766f52 --- /dev/null +++ b/pkg/domain/infra/abi/system_varlink.go @@ -0,0 +1,49 @@ +// +build varlink + +package abi + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" + iopodman "github.com/containers/libpod/pkg/varlink" + iopodmanAPI "github.com/containers/libpod/pkg/varlinkapi" + "github.com/containers/libpod/version" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/varlink/go/varlink" +) + +func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error { + var varlinkInterfaces = []*iopodman.VarlinkInterface{ + iopodmanAPI.New(opts.Command, ic.Libpod), + } + + service, err := varlink.NewService( + "Atomic", + "podman", + version.Version, + "https://github.com/containers/libpod", + ) + if err != nil { + return errors.Wrapf(err, "unable to create new varlink service") + } + + for _, i := range varlinkInterfaces { + if err := service.RegisterInterface(i); err != nil { + return errors.Errorf("unable to register varlink interface %v", i) + } + } + + // Run the varlink server at the given address + if err = service.Listen(opts.URI, opts.Timeout); err != nil { + switch err.(type) { + case varlink.ServiceTimeoutError: + logrus.Infof("varlink service expired (use --time to increase session time beyond %s ms, 0 means never timeout)", opts.Timeout.String()) + return nil + default: + return errors.Wrapf(err, "unable to start varlink service") + } + } + return nil +} diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index 7c9180d43..a57eadc63 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -213,6 +213,9 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo if fs.Changed("hooks-dir") { options = append(options, libpod.WithHooksDir(cfg.Engine.HooksDir...)) } + if fs.Changed("registries-conf") { + options = append(options, libpod.WithRegistriesConf(cfg.RegistriesConf)) + } // TODO flag to set CNI plugins dir? diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go index 93da3aeb4..6a08a1f85 100644 --- a/pkg/domain/infra/tunnel/events.go +++ b/pkg/domain/infra/tunnel/events.go @@ -25,6 +25,7 @@ func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptio for e := range binChan { opts.EventChan <- entities.ConvertToLibpodEvent(e) } + close(opts.EventChan) }() - return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters) + return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters, &opts.Stream) } diff --git a/pkg/network/network.go b/pkg/network/network.go index 5e9062019..526ee92d8 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -13,8 +13,11 @@ import ( "github.com/sirupsen/logrus" ) +// DefaultNetworkDriver is the default network type used +var DefaultNetworkDriver string = "bridge" + // SupportedNetworkDrivers describes the list of supported drivers -var SupportedNetworkDrivers = []string{"bridge"} +var SupportedNetworkDrivers = []string{DefaultNetworkDriver} // IsSupportedDriver checks if the user provided driver is supported func IsSupportedDriver(driver string) error { @@ -191,3 +194,16 @@ func InspectNetwork(config *config.Config, name string) (map[string]interface{}, err = json.Unmarshal(b, &rawList) return rawList, err } + +// Exists says whether a given network exists or not; it meant +// specifically for restful reponses so 404s can be used +func Exists(config *config.Config, name string) (bool, error) { + _, err := ReadRawCNIConfByName(config, name) + if err != nil { + if errors.Cause(err) == ErrNetworkNotFound { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 907063df9..ec96367cb 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -23,7 +23,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp filterFuncs []libpod.ContainerFilter pss []entities.ListContainer ) - all := options.All + all := options.All || options.Last > 0 if len(options.Filters) > 0 { for k, v := range options.Filters { for _, val := range v { diff --git a/pkg/signal/signal_common.go b/pkg/signal/signal_common.go new file mode 100644 index 000000000..8ff4b4dbf --- /dev/null +++ b/pkg/signal/signal_common.go @@ -0,0 +1,41 @@ +package signal + +import ( + "fmt" + "strconv" + "strings" + "syscall" +) + +// ParseSignal translates a string to a valid syscall signal. +// It returns an error if the signal map doesn't include the given signal. +func ParseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + if s == 0 { + return -1, fmt.Errorf("invalid signal: %s", rawSignal) + } + return syscall.Signal(s), nil + } + sig, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("invalid signal: %s", rawSignal) + } + return sig, nil +} + +// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input +// can be a name or number representation i.e. "KILL" "9" +func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) { + basename := strings.TrimPrefix(rawSignal, "-") + s, err := ParseSignal(basename) + if err == nil { + return s, nil + } + for k, v := range signalMap { + if k == strings.ToUpper(basename) { + return v, nil + } + } + return -1, fmt.Errorf("invalid signal: %s", basename) +} diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go index e6e0f1ca1..6eebf7e5a 100644 --- a/pkg/signal/signal_linux.go +++ b/pkg/signal/signal_linux.go @@ -8,11 +8,8 @@ package signal // NOTE: this package has originally been copied from github.com/docker/docker. import ( - "fmt" "os" "os/signal" - "strconv" - "strings" "syscall" "golang.org/x/sys/unix" @@ -94,23 +91,6 @@ var signalMap = map[string]syscall.Signal{ "RTMAX": sigrtmax, } -// ParseSignal translates a string to a valid syscall signal. -// It returns an error if the signal map doesn't include the given signal. -func ParseSignal(rawSignal string) (syscall.Signal, error) { - s, err := strconv.Atoi(rawSignal) - if err == nil { - if s == 0 { - return -1, fmt.Errorf("invalid signal: %s", rawSignal) - } - return syscall.Signal(s), nil - } - sig, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] - if !ok { - return -1, fmt.Errorf("invalid signal: %s", rawSignal) - } - return sig, nil -} - // CatchAll catches all signals and relays them to the specified channel. func CatchAll(sigc chan os.Signal) { var handledSigs []os.Signal @@ -125,19 +105,3 @@ func StopCatch(sigc chan os.Signal) { signal.Stop(sigc) close(sigc) } - -// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input -// can be a name or number representation i.e. "KILL" "9" -func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) { - basename := strings.TrimPrefix(rawSignal, "-") - s, err := ParseSignal(basename) - if err == nil { - return s, nil - } - for k, v := range signalMap { - if k == strings.ToUpper(basename) { - return v, nil - } - } - return -1, fmt.Errorf("invalid signal: %s", basename) -} diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go index f946d802d..9d1733c02 100644 --- a/pkg/signal/signal_unsupported.go +++ b/pkg/signal/signal_unsupported.go @@ -4,17 +4,88 @@ package signal import ( - "fmt" "os" "syscall" ) -const SIGWINCH = syscall.Signal(0xff) +const ( + sigrtmin = 34 + sigrtmax = 64 -// ParseSignal translates a string to a valid syscall signal. -// It returns an error if the signal map doesn't include the given signal. -func ParseSignal(rawSignal string) (syscall.Signal, error) { - return 0, fmt.Errorf("unsupported on non-linux platforms") + SIGWINCH = syscall.Signal(0xff) +) + +// signalMap is a map of Linux signals. +// These constants are sourced from the Linux version of golang.org/x/sys/unix +// (I don't see much risk of this changing). +// This should work as long as Podman only runs containers on Linux, which seems +// a safe assumption for now. +var signalMap = map[string]syscall.Signal{ + "ABRT": syscall.Signal(0x6), + "ALRM": syscall.Signal(0xe), + "BUS": syscall.Signal(0x7), + "CHLD": syscall.Signal(0x11), + "CLD": syscall.Signal(0x11), + "CONT": syscall.Signal(0x12), + "FPE": syscall.Signal(0x8), + "HUP": syscall.Signal(0x1), + "ILL": syscall.Signal(0x4), + "INT": syscall.Signal(0x2), + "IO": syscall.Signal(0x1d), + "IOT": syscall.Signal(0x6), + "KILL": syscall.Signal(0x9), + "PIPE": syscall.Signal(0xd), + "POLL": syscall.Signal(0x1d), + "PROF": syscall.Signal(0x1b), + "PWR": syscall.Signal(0x1e), + "QUIT": syscall.Signal(0x3), + "SEGV": syscall.Signal(0xb), + "STKFLT": syscall.Signal(0x10), + "STOP": syscall.Signal(0x13), + "SYS": syscall.Signal(0x1f), + "TERM": syscall.Signal(0xf), + "TRAP": syscall.Signal(0x5), + "TSTP": syscall.Signal(0x14), + "TTIN": syscall.Signal(0x15), + "TTOU": syscall.Signal(0x16), + "URG": syscall.Signal(0x17), + "USR1": syscall.Signal(0xa), + "USR2": syscall.Signal(0xc), + "VTALRM": syscall.Signal(0x1a), + "WINCH": syscall.Signal(0x1c), + "XCPU": syscall.Signal(0x18), + "XFSZ": syscall.Signal(0x19), + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, } // CatchAll catches all signals and relays them to the specified channel. @@ -26,9 +97,3 @@ func CatchAll(sigc chan os.Signal) { func StopCatch(sigc chan os.Signal) { panic("Unsupported on non-linux platforms") } - -// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input -// can be a name or number representation i.e. "KILL" "9" -func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) { - return 0, fmt.Errorf("unsupported on non-linux platforms") -} diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index 1b2a2ac32..f4cf0c704 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -72,13 +72,17 @@ func addPrivilegedDevices(g *generate.Generator) error { newMounts = append(newMounts, devMnt) } g.Config.Mounts = append(newMounts, g.Config.Mounts...) - g.Config.Linux.Resources.Devices = nil + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } } else { for _, d := range hostDevices { g.AddDevice(Device(d)) } // Add resources device - need to clear the existing one first. - g.Config.Linux.Resources.Devices = nil + if g.Config.Linux.Resources != nil { + g.Config.Linux.Resources.Devices = nil + } g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") } diff --git a/pkg/specgen/generate/config_linux_nocgo.go b/pkg/specgen/generate/config_linux_nocgo.go index fc8ed206d..81d1c7011 100644 --- a/pkg/specgen/generate/config_linux_nocgo.go +++ b/pkg/specgen/generate/config_linux_nocgo.go @@ -5,10 +5,11 @@ package generate import ( "errors" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" ) -func (s *specgen.SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { +func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { return nil, errors.New("not implemented") } |