diff options
Diffstat (limited to 'pkg')
68 files changed, 915 insertions, 409 deletions
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 2f8d151d8..39bd165d6 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -23,6 +23,7 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/containers/storage" + "github.com/docker/docker/pkg/jsonmessage" "github.com/gorilla/schema" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" @@ -325,16 +326,8 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { loop: // break out of for/select infinite loop for { - var report struct { - Stream string `json:"stream,omitempty"` - Status string `json:"status,omitempty"` - Progress struct { - Current uint64 `json:"current,omitempty"` - Total int64 `json:"total,omitempty"` - } `json:"progressDetail,omitempty"` - Error string `json:"error,omitempty"` - Id string `json:"id,omitempty"` //nolint:revive,stylecheck - } + report := jsonmessage.JSONMessage{} + report.Progress = &jsonmessage.JSONProgress{} select { case e := <-progress: switch e.Event { @@ -342,14 +335,15 @@ loop: // break out of for/select infinite loop report.Status = "Pulling fs layer" case types.ProgressEventRead: report.Status = "Downloading" - report.Progress.Current = e.Offset + report.Progress.Current = int64(e.Offset) report.Progress.Total = e.Artifact.Size + report.ProgressMessage = report.Progress.String() case types.ProgressEventSkipped: report.Status = "Already exists" case types.ProgressEventDone: report.Status = "Download complete" } - report.Id = e.Artifact.Digest.Encoded()[0:12] + report.ID = e.Artifact.Digest.Encoded()[0:12] if err := enc.Encode(report); err != nil { logrus.Warnf("Failed to json encode error %q", err.Error()) } @@ -358,7 +352,11 @@ loop: // break out of for/select infinite loop err := pullRes.err pulledImages := pullRes.images if err != nil { - report.Error = err.Error() + msg := err.Error() + report.Error = &jsonmessage.JSONError{ + Message: msg, + } + report.ErrorMessage = msg } else { if len(pulledImages) > 0 { img := pulledImages[0].ID() @@ -367,9 +365,13 @@ loop: // break out of for/select infinite loop } else { report.Status = "Download complete" } - report.Id = img[0:12] + report.ID = img[0:12] } else { - report.Error = "internal error: no images pulled" + msg := "internal error: no images pulled" + report.Error = &jsonmessage.JSONError{ + Message: msg, + } + report.ErrorMessage = msg } } if err := enc.Encode(report); err != nil { diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index a9185c3d3..15cfc824e 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -140,6 +140,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Registry: "docker.io", Rm: true, ShmSize: 64 * 1024 * 1024, + TLSVerify: true, } decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go index bb82ef10d..f29808124 100644 --- a/pkg/api/handlers/compat/images_push.go +++ b/pkg/api/handlers/compat/images_push.go @@ -156,6 +156,7 @@ loop: // break out of for/select infinite loop Current: int64(e.Offset), Total: e.Artifact.Size, } + report.ProgressMessage = report.Progress.String() case types.ProgressEventSkipped: report.Status = "Layer already exists" case types.ProgressEventDone: diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index a6fd3a3a1..2fc95e84e 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -26,6 +26,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { ListTags bool `json:"listTags"` }{ // This is where you can override the golang default value for one of fields + TLSVerify: true, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 65177218a..29d1398cf 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -23,6 +23,13 @@ import ( "github.com/sirupsen/logrus" ) +func normalizeNetworkName(rt *libpod.Runtime, name string) (string, bool) { + if name == nettypes.BridgeNetworkDriver { + return rt.Network().DefaultNetworkName(), true + } + return name, false +} + func InspectNetwork(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) @@ -44,13 +51,13 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { utils.Error(w, http.StatusBadRequest, define.ErrInvalidArg) return } - name := utils.GetName(r) + name, changed := normalizeNetworkName(runtime, utils.GetName(r)) net, err := runtime.Network().NetworkInspect(name) if err != nil { utils.NetworkNotFound(w, name, err) return } - report, err := convertLibpodNetworktoDockerNetwork(runtime, net) + report, err := convertLibpodNetworktoDockerNetwork(runtime, &net, changed) if err != nil { utils.InternalServerError(w, err) return @@ -58,7 +65,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, report) } -func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network nettypes.Network) (*types.NetworkResource, error) { +func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network *nettypes.Network, changeDefaultName bool) (*types.NetworkResource, error) { cons, err := runtime.GetAllContainers() if err != nil { return nil, err @@ -107,11 +114,15 @@ func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network nettyp Config: ipamConfigs, } + name := network.Name + if changeDefaultName && name == runtime.Network().DefaultNetworkName() { + name = nettypes.BridgeNetworkDriver + } report := types.NetworkResource{ - Name: network.Name, - ID: network.ID, - Driver: network.Driver, - // TODO add Created: , + Name: name, + ID: network.ID, + Driver: network.Driver, + Created: network.Created, Internal: network.Internal, EnableIPv6: network.IPv6Enabled, Labels: network.Labels, @@ -149,7 +160,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { } reports := make([]*types.NetworkResource, 0, len(nets)) for _, net := range nets { - report, err := convertLibpodNetworktoDockerNetwork(runtime, net) + report, err := convertLibpodNetworktoDockerNetwork(runtime, &net, true) if err != nil { utils.InternalServerError(w, err) return @@ -182,27 +193,22 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { network.Options = make(map[string]string) - // TODO: we should consider making this constants in c/common/libnetwork/types + // dockers bridge networks are always isolated from each other + if network.Driver == nettypes.BridgeNetworkDriver { + network.Options[nettypes.IsolateOption] = "true" + } + for opt, optVal := range networkCreate.Options { switch opt { - case "mtu": + case nettypes.MTUOption: fallthrough case "com.docker.network.driver.mtu": - if network.Driver == nettypes.BridgeNetworkDriver { - network.Options["mtu"] = optVal - } - case "icc": - fallthrough - case "com.docker.network.bridge.enable_icc": - // TODO: needs to be implemented - if network.Driver == nettypes.BridgeNetworkDriver { - responseWarning = "com.docker.network.bridge.enable_icc is not currently implemented" - } + network.Options[nettypes.MTUOption] = optVal case "com.docker.network.bridge.name": if network.Driver == nettypes.BridgeNetworkDriver { network.NetworkInterface = optVal } - case "mode": + case nettypes.ModeOption: if network.Driver == nettypes.MacVLANNetworkDriver || network.Driver == nettypes.IPVLANNetworkDriver { network.Options[opt] = optVal } @@ -305,7 +311,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { Timeout: query.Timeout, } - name := utils.GetName(r) + name, _ := normalizeNetworkName(runtime, utils.GetName(r)) reports, err := ic.NetworkRm(r.Context(), []string{name}, options) if err != nil { utils.Error(w, http.StatusInternalServerError, err) @@ -340,7 +346,7 @@ func Connect(w http.ResponseWriter, r *http.Request) { netOpts := nettypes.PerNetworkOptions{} - name := utils.GetName(r) + name, _ := normalizeNetworkName(runtime, utils.GetName(r)) if netConnect.EndpointConfig != nil { if netConnect.EndpointConfig.Aliases != nil { netOpts.Aliases = netConnect.EndpointConfig.Aliases @@ -416,7 +422,7 @@ func Disconnect(w http.ResponseWriter, r *http.Request) { return } - name := utils.GetName(r) + name, _ := normalizeNetworkName(runtime, utils.GetName(r)) err := runtime.DisconnectContainerFromNetwork(netDisconnect.Container, name, netDisconnect.Force) if err != nil { if errors.Is(err, define.ErrNoSuchCtr) { diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index e4964d602..1307c267a 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -31,6 +31,9 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { ContainerNetworkConfig: specgen.ContainerNetworkConfig{ UseImageHosts: conf.Containers.NoHosts, }, + ContainerSecurityConfig: specgen.ContainerSecurityConfig{ + Umask: conf.Containers.Umask, + }, } if err := json.NewDecoder(r.Body).Decode(&sg); err != nil { diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index ed1c65f8e..67943ecf1 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -1,7 +1,6 @@ package libpod import ( - "context" "errors" "fmt" "io" @@ -14,13 +13,11 @@ import ( "github.com/containers/buildah" "github.com/containers/common/libimage" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/types" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/api/handlers" "github.com/containers/podman/v4/pkg/api/handlers/utils" api "github.com/containers/podman/v4/pkg/api/types" - "github.com/containers/podman/v4/pkg/auth" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/entities/reports" "github.com/containers/podman/v4/pkg/domain/infra/abi" @@ -416,74 +413,6 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, report) } -// PushImage is the handler for the compat http endpoint for pushing images. -func PushImage(w http.ResponseWriter, r *http.Request) { - decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - - query := struct { - All bool `schema:"all"` - Destination string `schema:"destination"` - Format string `schema:"format"` - RemoveSignatures bool `schema:"removeSignatures"` - TLSVerify bool `schema:"tlsVerify"` - }{ - // This is where you can override the golang default value for one of fields - } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) - return - } - - source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path - if _, err := utils.ParseStorageReference(source); err != nil { - utils.Error(w, http.StatusBadRequest, err) - return - } - - destination := query.Destination - if destination == "" { - destination = source - } - - if err := utils.IsRegistryReference(destination); err != nil { - utils.Error(w, http.StatusBadRequest, err) - return - } - - authconf, authfile, err := auth.GetCredentials(r) - if err != nil { - utils.Error(w, http.StatusBadRequest, err) - return - } - defer auth.RemoveAuthfile(authfile) - var username, password string - if authconf != nil { - username = authconf.Username - password = authconf.Password - } - options := entities.ImagePushOptions{ - All: query.All, - Authfile: authfile, - Format: query.Format, - Password: password, - Quiet: true, - RemoveSignatures: query.RemoveSignatures, - Username: username, - } - if _, found := r.URL.Query()["tlsVerify"]; found { - options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) - } - - imageEngine := abi.ImageEngine{Libpod: runtime} - if err := imageEngine.Push(context.Background(), source, destination, options); err != nil { - utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err)) - return - } - - utils.WriteResponse(w, http.StatusOK, "") -} - func CommitContainer(w http.ResponseWriter, r *http.Request) { var ( destImage string diff --git a/pkg/api/handlers/libpod/images_push.go b/pkg/api/handlers/libpod/images_push.go new file mode 100644 index 000000000..9ee651f5b --- /dev/null +++ b/pkg/api/handlers/libpod/images_push.go @@ -0,0 +1,145 @@ +package libpod + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/containers/image/v5/types" + "github.com/containers/podman/v4/libpod" + "github.com/containers/podman/v4/pkg/api/handlers/utils" + api "github.com/containers/podman/v4/pkg/api/types" + "github.com/containers/podman/v4/pkg/auth" + "github.com/containers/podman/v4/pkg/channel" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/domain/infra/abi" + "github.com/gorilla/schema" + "github.com/sirupsen/logrus" +) + +// PushImage is the handler for the compat http endpoint for pushing images. +func PushImage(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + + query := struct { + All bool `schema:"all"` + Destination string `schema:"destination"` + Format string `schema:"format"` + RemoveSignatures bool `schema:"removeSignatures"` + TLSVerify bool `schema:"tlsVerify"` + Quiet bool `schema:"quiet"` + }{ + TLSVerify: true, + // #14971: older versions did not sent *any* data, so we need + // to be quiet by default to remain backwards compatible + Quiet: true, + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) + return + } + + source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path + if _, err := utils.ParseStorageReference(source); err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + + destination := query.Destination + if destination == "" { + destination = source + } + + if err := utils.IsRegistryReference(destination); err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + + authconf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + defer auth.RemoveAuthfile(authfile) + + var username, password string + if authconf != nil { + username = authconf.Username + password = authconf.Password + } + options := entities.ImagePushOptions{ + All: query.All, + Authfile: authfile, + Format: query.Format, + Password: password, + Quiet: true, + RemoveSignatures: query.RemoveSignatures, + Username: username, + } + + if _, found := r.URL.Query()["tlsVerify"]; found { + options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + + imageEngine := abi.ImageEngine{Libpod: runtime} + + // Let's keep thing simple when running in quiet mode and push directly. + if query.Quiet { + if err := imageEngine.Push(context.Background(), source, destination, options); err != nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err)) + return + } + utils.WriteResponse(w, http.StatusOK, "") + return + } + + writer := channel.NewWriter(make(chan []byte)) + defer writer.Close() + options.Writer = writer + + pushCtx, pushCancel := context.WithCancel(r.Context()) + var pushError error + go func() { + defer pushCancel() + pushError = imageEngine.Push(pushCtx, source, destination, options) + }() + + flush := func() { + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + flush() + + enc := json.NewEncoder(w) + enc.SetEscapeHTML(true) + for { + var report entities.ImagePushReport + select { + case s := <-writer.Chan(): + report.Stream = string(s) + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to encode json: %v", err) + } + flush() + case <-pushCtx.Done(): + if pushError != nil { + report.Error = pushError.Error() + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to encode json: %v", err) + } + } + flush() + return + case <-r.Context().Done(): + // Client has closed connection + return + } + } +} diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index 3235a2972..43c7139d3 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -310,6 +310,7 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) { TLSVerify bool `schema:"tlsVerify"` }{ // Add defaults here once needed. + TLSVerify: true, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusBadRequest, diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go index a3aa681d0..57cafa5f6 100644 --- a/pkg/api/server/handler_api.go +++ b/pkg/api/server/handler_api.go @@ -1,7 +1,10 @@ package server import ( + "bufio" + "errors" "fmt" + "net" "net/http" "runtime" @@ -9,34 +12,54 @@ import ( "github.com/sirupsen/logrus" ) +type BufferedResponseWriter struct { + b *bufio.Writer + w http.ResponseWriter +} + // APIHandler is a wrapper to enhance HandlerFunc's and remove redundant code func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Wrapper to hide some boilerplate - fn := func(w http.ResponseWriter, r *http.Request) { - if err := r.ParseForm(); err != nil { - logrus.WithFields(logrus.Fields{ - "X-Reference-Id": r.Header.Get("X-Reference-Id"), - }).Info("Failed Request: unable to parse form: " + err.Error()) - } + s.apiWrapper(h, w, r, false) + } +} + +// An API Handler to help historical clients with broken parsing that expect +// streaming JSON payloads to be reliably messaged framed (full JSON record +// always fits in each read()) +func (s *APIServer) StreamBufferedAPIHandler(h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Wrapper to hide some boilerplate + s.apiWrapper(h, w, r, true) + } +} - cv := version.APIVersion[version.Compat][version.CurrentAPI] - w.Header().Set("API-Version", fmt.Sprintf("%d.%d", cv.Major, cv.Minor)) +func (s *APIServer) apiWrapper(h http.HandlerFunc, w http.ResponseWriter, r *http.Request, buffer bool) { + if err := r.ParseForm(); err != nil { + logrus.WithFields(logrus.Fields{ + "X-Reference-Id": r.Header.Get("X-Reference-Id"), + }).Info("Failed Request: unable to parse form: " + err.Error()) + } - lv := version.APIVersion[version.Libpod][version.CurrentAPI].String() - w.Header().Set("Libpod-API-Version", lv) - w.Header().Set("Server", "Libpod/"+lv+" ("+runtime.GOOS+")") + cv := version.APIVersion[version.Compat][version.CurrentAPI] + w.Header().Set("API-Version", fmt.Sprintf("%d.%d", cv.Major, cv.Minor)) - if s.CorsHeaders != "" { - w.Header().Set("Access-Control-Allow-Origin", s.CorsHeaders) - w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth, Connection, Upgrade, X-Registry-Config") - w.Header().Set("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS") - } + lv := version.APIVersion[version.Libpod][version.CurrentAPI].String() + w.Header().Set("Libpod-API-Version", lv) + w.Header().Set("Server", "Libpod/"+lv+" ("+runtime.GOOS+")") - h(w, r) - } - fn(w, r) + if s.CorsHeaders != "" { + w.Header().Set("Access-Control-Allow-Origin", s.CorsHeaders) + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth, Connection, Upgrade, X-Registry-Config") + w.Header().Set("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS") } + + if buffer { + w = newBufferedResponseWriter(w) + } + + h(w, r) } // VersionedPath prepends the version parsing code @@ -44,3 +67,37 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { func VersionedPath(p string) string { return "/v{version:[0-9][0-9A-Za-z.-]*}" + p } + +func (w *BufferedResponseWriter) Header() http.Header { + return w.w.Header() +} + +func (w *BufferedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + _ = w.b.Flush() + if wrapped, ok := w.w.(http.Hijacker); ok { + return wrapped.Hijack() + } + + return nil, nil, errors.New("ResponseWriter does not support hijacking") +} + +func (w *BufferedResponseWriter) Write(b []byte) (int, error) { + return w.b.Write(b) +} + +func (w *BufferedResponseWriter) WriteHeader(statusCode int) { + w.w.WriteHeader(statusCode) +} + +func (w *BufferedResponseWriter) Flush() { + _ = w.b.Flush() + if wrapped, ok := w.w.(http.Flusher); ok { + wrapped.Flush() + } +} +func newBufferedResponseWriter(rw http.ResponseWriter) *BufferedResponseWriter { + return &BufferedResponseWriter{ + bufio.NewWriterSize(rw, 8192), + rw, + } +} diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index e2ecdb6af..b319fc14a 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -397,9 +397,9 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // $ref: "#/responses/containerNotFound" // 500: // $ref: "#/responses/internalError" - r.HandleFunc(VersionedPath("/containers/{name}/stats"), s.APIHandler(compat.StatsContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/containers/{name}/stats"), s.StreamBufferedAPIHandler(compat.StatsContainer)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths - r.HandleFunc("/containers/{name}/stats", s.APIHandler(compat.StatsContainer)).Methods(http.MethodGet) + r.HandleFunc("/containers/{name}/stats", s.StreamBufferedAPIHandler(compat.StatsContainer)).Methods(http.MethodGet) // swagger:operation POST /containers/{name}/stop compat ContainerStop // --- // tags: @@ -455,9 +455,9 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // $ref: "#/responses/containerNotFound" // 500: // $ref: "#/responses/internalError" - r.HandleFunc(VersionedPath("/containers/{name}/top"), s.APIHandler(compat.TopContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/containers/{name}/top"), s.StreamBufferedAPIHandler(compat.TopContainer)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths - r.HandleFunc("/containers/{name}/top", s.APIHandler(compat.TopContainer)).Methods(http.MethodGet) + r.HandleFunc("/containers/{name}/top", s.StreamBufferedAPIHandler(compat.TopContainer)).Methods(http.MethodGet) // swagger:operation POST /containers/{name}/unpause compat ContainerUnpause // --- // tags: diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go index 76f9ec619..3442b3eab 100644 --- a/pkg/api/server/register_events.go +++ b/pkg/api/server/register_events.go @@ -34,9 +34,9 @@ func (s *APIServer) registerEventsHandlers(r *mux.Router) error { // description: returns a string of json data describing an event // 500: // "$ref": "#/responses/internalError" - r.Handle(VersionedPath("/events"), s.APIHandler(compat.GetEvents)).Methods(http.MethodGet) + r.Handle(VersionedPath("/events"), s.StreamBufferedAPIHandler(compat.GetEvents)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths - r.Handle("/events", s.APIHandler(compat.GetEvents)).Methods(http.MethodGet) + r.Handle("/events", s.StreamBufferedAPIHandler(compat.GetEvents)).Methods(http.MethodGet) // swagger:operation GET /libpod/events system SystemEventsLibpod // --- // tags: diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index a2f46cb35..d71e0d470 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -192,8 +192,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - in: query // name: tlsVerify // type: boolean - // default: false - // description: skip TLS verification for registries + // default: true + // description: Require HTTPS and verify signatures when contacting registries. // - in: query // name: listTags // type: boolean @@ -702,9 +702,9 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $ref: "#/responses/badParamError" // 500: // $ref: "#/responses/internalError" - r.Handle(VersionedPath("/build"), s.APIHandler(compat.BuildImage)).Methods(http.MethodPost) + r.Handle(VersionedPath("/build"), s.StreamBufferedAPIHandler(compat.BuildImage)).Methods(http.MethodPost) // Added non version path to URI to support docker non versioned paths - r.Handle("/build", s.APIHandler(compat.BuildImage)).Methods(http.MethodPost) + r.Handle("/build", s.StreamBufferedAPIHandler(compat.BuildImage)).Methods(http.MethodPost) /* libpod endpoints */ @@ -730,6 +730,11 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: Require TLS verification. // type: boolean // default: true + // - in: query + // name: quiet + // description: "silences extra stream data on push" + // type: boolean + // default: true // - in: header // name: X-Registry-Auth // type: string @@ -1115,8 +1120,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - in: query // name: tlsVerify // type: boolean - // default: false - // description: skip TLS verification for registries + // default: true + // description: Require HTTPS and verify signatures when contacting registries. // - in: query // name: listTags // type: boolean diff --git a/pkg/api/server/register_manifest.go b/pkg/api/server/register_manifest.go index 4fadb92fd..19b507047 100644 --- a/pkg/api/server/register_manifest.go +++ b/pkg/api/server/register_manifest.go @@ -69,12 +69,12 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // name: all // description: push all images // type: boolean - // default: false + // default: true // - in: query // name: tlsVerify // type: boolean - // default: false - // description: skip TLS verification for registries + // default: true + // description: Require HTTPS and verify signatures when contacting registries. // responses: // 200: // schema: @@ -195,8 +195,8 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // - in: query // name: tlsVerify // type: boolean - // default: false - // description: skip TLS verification for registries + // default: true + // description: Require HTTPS and verify signatures when contacting registries. // - in: body // name: options // description: options for mutating a manifest diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index cd5147629..bb7867c4e 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -267,46 +267,6 @@ func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities return &report, response.Process(&report) } -// Push is the binding for libpod's v2 endpoints for push images. Note that -// `source` must be a referring to an image in the remote's container storage. -// The destination must be a reference to a registry (i.e., of docker transport -// or be normalized to one). Other transports are rejected as they do not make -// sense in a remote context. -func Push(ctx context.Context, source string, destination string, options *PushOptions) error { - if options == nil { - options = new(PushOptions) - } - conn, err := bindings.GetClient(ctx) - if err != nil { - return err - } - header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) - if err != nil { - return err - } - - params, err := options.ToParams() - if err != nil { - return err - } - // SkipTLSVerify is special. We need to delete the param added by - // toparams and change the key and flip the bool - if options.SkipTLSVerify != nil { - params.Del("SkipTLSVerify") - params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) - } - params.Set("destination", destination) - - path := fmt.Sprintf("/images/%s/push", source) - response, err := conn.DoRequest(ctx, nil, http.MethodPost, path, params, header) - if err != nil { - return err - } - defer response.Body.Close() - - return response.Process(err) -} - // Search is the binding for libpod's v2 endpoints for Search images. func Search(ctx context.Context, term string, options *SearchOptions) ([]entities.ImageSearchReport, error) { if options == nil { diff --git a/pkg/bindings/images/push.go b/pkg/bindings/images/push.go new file mode 100644 index 000000000..8db3726e6 --- /dev/null +++ b/pkg/bindings/images/push.go @@ -0,0 +1,96 @@ +package images + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "strconv" + + imageTypes "github.com/containers/image/v5/types" + "github.com/containers/podman/v4/pkg/auth" + "github.com/containers/podman/v4/pkg/bindings" + "github.com/containers/podman/v4/pkg/domain/entities" +) + +// Push is the binding for libpod's endpoints for push images. Note that +// `source` must be a referring to an image in the remote's container storage. +// The destination must be a reference to a registry (i.e., of docker transport +// or be normalized to one). Other transports are rejected as they do not make +// sense in a remote context. +func Push(ctx context.Context, source string, destination string, options *PushOptions) error { + if options == nil { + options = new(PushOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return err + } + + params, err := options.ToParams() + if err != nil { + return err + } + // SkipTLSVerify is special. We need to delete the param added by + // toparams and change the key and flip the bool + if options.SkipTLSVerify != nil { + params.Del("SkipTLSVerify") + params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) + } + params.Set("destination", destination) + + path := fmt.Sprintf("/images/%s/push", source) + response, err := conn.DoRequest(ctx, nil, http.MethodPost, path, params, header) + if err != nil { + return err + } + defer response.Body.Close() + + if !response.IsSuccess() { + return response.Process(err) + } + + // Historically push writes status to stderr + writer := io.Writer(os.Stderr) + if options.GetQuiet() { + writer = ioutil.Discard + } + + dec := json.NewDecoder(response.Body) + for { + var report entities.ImagePushReport + if err := dec.Decode(&report); err != nil { + if errors.Is(err, io.EOF) { + break + } + return err + } + + select { + case <-response.Request.Context().Done(): + break + default: + // non-blocking select + } + + switch { + case report.Stream != "": + fmt.Fprint(writer, report.Stream) + case report.Error != "": + // There can only be one error. + return errors.New(report.Error) + default: + return fmt.Errorf("failed to parse push results stream, unexpected input: %v", report) + } + } + + return nil +} diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 3728ae5c0..0e672cdea 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -133,6 +133,8 @@ type PushOptions struct { RemoveSignatures *bool // Username for authenticating against the registry. Username *string + // Quiet can be specified to suppress progress when pushing. + Quiet *bool } //go:generate go run ../generator/generator.go SearchOptions diff --git a/pkg/bindings/images/types_push_options.go b/pkg/bindings/images/types_push_options.go index 25f6c5546..63a19fb81 100644 --- a/pkg/bindings/images/types_push_options.go +++ b/pkg/bindings/images/types_push_options.go @@ -136,3 +136,18 @@ func (o *PushOptions) GetUsername() string { } return *o.Username } + +// WithQuiet set field Quiet to given value +func (o *PushOptions) WithQuiet(value bool) *PushOptions { + o.Quiet = &value + return o +} + +// GetQuiet returns value of field Quiet +func (o *PushOptions) GetQuiet() bool { + if o.Quiet == nil { + var z bool + return z + } + return *o.Quiet +} diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index a005be6ac..8f76ce456 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -120,8 +120,6 @@ var _ = Describe("Podman images", func() { // deleting hence image cannot be deleted until the container is deleted. _, errs = images.Remove(bt.conn, []string{alpine.shortName}, nil) code, _ = bindings.CheckResponseCode(errs[0]) - // FIXME FIXME FIXME: #12441: another invalid error - // FIXME FIXME FIXME: this time msg="Image used by SHA: ..." Expect(code).To(BeNumerically("==", -1)) // Removing the image "alpine" where force = true diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 934a7cbdc..df793034b 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -71,12 +71,15 @@ type StringSliceReport struct { } type PauseUnPauseOptions struct { - All bool + Filters map[string][]string + All bool + Latest bool } type PauseUnpauseReport struct { - Err error - Id string //nolint:revive,stylecheck + Err error + Id string //nolint:revive,stylecheck + RawInput string } type StopOptions struct { diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index da317cfad..b8b346005 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -1,6 +1,7 @@ package entities import ( + "io" "net/url" "time" @@ -192,8 +193,7 @@ type ImagePushOptions struct { // image. Default is manifest type of source, with fallbacks. // Ignored for remote calls. Format string - // Quiet can be specified to suppress pull progress when pulling. Ignored - // for remote calls. + // Quiet can be specified to suppress push progress when pushing. Quiet bool // Rm indicates whether to remove the manifest list if push succeeds Rm bool @@ -211,6 +211,17 @@ type ImagePushOptions struct { Progress chan types.ProgressProperties // CompressionFormat is the format to use for the compression of the blobs CompressionFormat string + // Writer is used to display copy information including progress bars. + Writer io.Writer +} + +// ImagePushReport is the response from pushing an image. +// Currently only used in the remote API. +type ImagePushReport struct { + // Stream used to provide push progress + Stream string `json:"stream,omitempty"` + // Error contains text of errors from pushing + Error string `json:"error,omitempty"` } // ImageSearchOptions are the arguments for searching images. diff --git a/pkg/domain/entities/machine.go b/pkg/domain/entities/machine.go index 6ba53dbd1..4fd0413c9 100644 --- a/pkg/domain/entities/machine.go +++ b/pkg/domain/entities/machine.go @@ -1,5 +1,7 @@ package entities +import "github.com/containers/podman/v4/libpod/define" + type ListReporter struct { Name string Default bool @@ -16,3 +18,23 @@ type ListReporter struct { RemoteUsername string IdentityPath string } + +// MachineInfo contains info on the machine host and version info +type MachineInfo struct { + Host *MachineHostInfo `json:"Host"` + Version define.Version `json:"Version"` +} + +// MachineHostInfo contains info on the machine host +type MachineHostInfo struct { + Arch string `json:"Arch"` + CurrentMachine string `json:"CurrentMachine"` + DefaultMachine string `json:"DefaultMachine"` + EventsDir string `json:"EventsDir"` + MachineConfigDir string `json:"MachineConfigDir"` + MachineImageDir string `json:"MachineImageDir"` + MachineState string `json:"MachineState"` + NumberOfMachines int `json:"NumberOfMachines"` + OS string `json:"OS"` + VMType string `json:"VMType"` +} diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index 35a5d8a4a..5bb438d7d 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -88,6 +88,7 @@ type PlayKubeReport struct { // Volumes - volumes created by play kube. Volumes []PlayKubeVolume PlayKubeTeardown + Secrets []PlaySecret } type KubePlayReport = PlayKubeReport @@ -100,3 +101,7 @@ type PlayKubeTeardown struct { StopReport []*PodStopReport RmReport []*PodRmReport } + +type PlaySecret struct { + CreateReport *SecretCreateReport +} diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index f88a165e7..de62b6582 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/containers/common/pkg/filters" cutil "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" @@ -24,7 +25,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo case "label": // we have to match that all given labels exits on that container return func(c *libpod.Container) bool { - return util.MatchLabelFilters(filterValues, c.Labels()) + return filters.MatchLabelFilters(filterValues, c.Labels()) }, nil case "name": // we only have to match one name @@ -299,7 +300,11 @@ func GeneratePruneContainerFilterFuncs(filter string, filterValues []string, r * switch filter { case "label": return func(c *libpod.Container) bool { - return util.MatchLabelFilters(filterValues, c.Labels()) + return filters.MatchLabelFilters(filterValues, c.Labels()) + }, nil + case "label!": + return func(c *libpod.Container) bool { + return !filters.MatchLabelFilters(filterValues, c.Labels()) }, nil case "until": return prepareUntilFilterFunc(filterValues) diff --git a/pkg/domain/filters/pods.go b/pkg/domain/filters/pods.go index 78b97db64..7b0944292 100644 --- a/pkg/domain/filters/pods.go +++ b/pkg/domain/filters/pods.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/containers/common/pkg/filters" cutil "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" @@ -115,7 +116,7 @@ func GeneratePodFilterFunc(filter string, filterValues []string, r *libpod.Runti case "label": return func(p *libpod.Pod) bool { labels := p.Labels() - return util.MatchLabelFilters(filterValues, labels) + return filters.MatchLabelFilters(filterValues, labels) }, nil case "until": return func(p *libpod.Pod) bool { diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index 7c5047225..9cec39fbb 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -6,6 +6,7 @@ import ( "regexp" "strings" + pruneFilters "github.com/containers/common/pkg/filters" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/pkg/util" ) @@ -36,7 +37,7 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { case "label": filter := val vf = append(vf, func(v *libpod.Volume) bool { - return util.MatchLabelFilters([]string{filter}, v.Labels()) + return pruneFilters.MatchLabelFilters([]string{filter}, v.Labels()) }) case "opt": filterArray := strings.SplitN(val, "=", 2) @@ -100,7 +101,7 @@ func GeneratePruneVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, erro switch filter { case "label": vf = append(vf, func(v *libpod.Volume) bool { - return util.MatchLabelFilters([]string{filterVal}, v.Labels()) + return pruneFilters.MatchLabelFilters([]string{filterVal}, v.Labels()) }) case "until": f, err := createUntilFilterVolumeFunction(filterVal) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 04eb85504..783224e9c 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -62,6 +62,11 @@ func getContainersAndInputByContext(all, latest bool, names []string, filters ma } case all: ctrs, err = runtime.GetAllContainers() + if err == nil { + for _, ctr := range ctrs { + rawInput = append(rawInput, ctr.ID()) + } + } case latest: ctr, err = runtime.GetLatestContainer() if err == nil { @@ -133,37 +138,57 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin } func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { - ctrs, err := getContainersByContext(options.All, false, namesOrIds, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, options.Filters, ic.Libpod) if err != nil { return nil, err } - report := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) + ctrMap := map[string]string{} + if len(rawInputs) == len(ctrs) { + for i := range ctrs { + ctrMap[ctrs[i].ID()] = rawInputs[i] + } + } + reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := c.Pause() if err != nil && options.All && errors.Is(err, define.ErrCtrStateInvalid) { logrus.Debugf("Container %s is not running", c.ID()) continue } - report = append(report, &entities.PauseUnpauseReport{Id: c.ID(), Err: err}) + reports = append(reports, &entities.PauseUnpauseReport{ + Id: c.ID(), + Err: err, + RawInput: ctrMap[c.ID()], + }) } - return report, nil + return reports, nil } func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { - ctrs, err := getContainersByContext(options.All, false, namesOrIds, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, options.Filters, ic.Libpod) if err != nil { return nil, err } - report := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) + ctrMap := map[string]string{} + if len(rawInputs) == len(ctrs) { + for i := range ctrs { + ctrMap[ctrs[i].ID()] = rawInputs[i] + } + } + reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := c.Unpause() if err != nil && options.All && errors.Is(err, define.ErrCtrStateInvalid) { logrus.Debugf("Container %s is not paused", c.ID()) continue } - report = append(report, &entities.PauseUnpauseReport{Id: c.ID(), Err: err}) + reports = append(reports, &entities.PauseUnpauseReport{ + Id: c.ID(), + Err: err, + RawInput: ctrMap[c.ID()], + }) } - return report, nil + return reports, nil } func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) { names := namesOrIds @@ -235,6 +260,7 @@ func (ic *ContainerEngine) ContainerPrune(ctx context.Context, options entities. if err != nil { return nil, err } + filterFuncs = append(filterFuncs, generatedFunc) } return ic.Libpod.PruneContainers(filterFuncs) @@ -1035,6 +1061,15 @@ func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts e } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { + removeContainer := func(ctr *libpod.Container, force bool) error { + var timeout *uint + if err := ic.Libpod.RemoveContainer(ctx, ctr, force, true, timeout); err != nil { + logrus.Debugf("unable to remove container %s after failing to start and attach to it: %v", ctr.ID(), err) + return err + } + return nil + } + warn, err := generate.CompleteSpec(ctx, ic.Libpod, opts.Spec) if err != nil { return nil, err @@ -1055,6 +1090,8 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if opts.CIDFile != "" { if err := util.CreateCidFile(opts.CIDFile, ctr.ID()); err != nil { + // If you fail to create CIDFile then remove the container + _ = removeContainer(ctr, true) return nil, err } } @@ -1072,6 +1109,11 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if err := ctr.Start(ctx, true); err != nil { // This means the command did not exist report.ExitCode = define.ExitCode(err) + if opts.Rm { + if rmErr := removeContainer(ctr, true); rmErr != nil && !errors.Is(rmErr, define.ErrNoSuchCtr) { + logrus.Errorf("Container %s failed to be removed", ctr.ID()) + } + } return &report, err } @@ -1088,10 +1130,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta return &report, nil } if opts.Rm { - var timeout *uint - if deleteError := ic.Libpod.RemoveContainer(ctx, ctr, true, false, timeout); deleteError != nil { - logrus.Debugf("unable to remove container %s after failing to start and attach to it", ctr.ID()) - } + _ = removeContainer(ctr, true) } if errors.Is(err, define.ErrWillDeadlock) { logrus.Debugf("Deadlock error on %q: %v", ctr.ID(), err) @@ -1103,8 +1142,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } report.ExitCode = ic.GetContainerExitCode(ctx, ctr) if opts.Rm && !ctr.ShouldRestart(ctx) { - var timeout *uint - if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true, timeout); err != nil { + if err := removeContainer(ctr, false); err != nil { if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { logrus.Infof("Container %s was already removed, skipping --rm", ctr.ID()) diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 38008c7b9..ff42b0367 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -305,6 +305,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri pushOptions.RemoveSignatures = options.RemoveSignatures pushOptions.SignBy = options.SignBy pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + pushOptions.Writer = options.Writer compressionFormat := options.CompressionFormat if compressionFormat == "" { @@ -322,7 +323,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri pushOptions.CompressionFormat = &algo } - if !options.Quiet { + if !options.Quiet && pushOptions.Writer == nil { pushOptions.Writer = os.Stderr } diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index d20744d76..bdc3d9513 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -331,7 +331,8 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin } if opts.Rm { - if _, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, []string{manifestList.ID()}, nil); len(rmErrors) > 0 { + rmOpts := &libimage.RemoveImagesOptions{LookupManifest: true} + if _, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, []string{manifestList.ID()}, rmOpts); len(rmErrors) > 0 { return "", fmt.Errorf("error removing manifest after push: %w", rmErrors[0]) } } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 8b47eff53..3f2fd5f92 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -84,15 +84,15 @@ func (ic *ContainerEngine) createServiceContainer(ctx context.Context, name stri return ctr, nil } -// Creates the name for a service container based on the provided content of a -// K8s yaml file. -func serviceContainerName(content []byte) string { +// Creates the name for a k8s entity based on the provided content of a +// K8s yaml file and a given suffix. +func k8sName(content []byte, suffix string) string { // The name of the service container is the first 12 // characters of the yaml file's hash followed by the // '-service' suffix to guarantee a predictable and // discoverable name. hash := digest.FromBytes(content).Encoded() - return hash[0:12] + "-service" + return hash[0:12] + "-" + suffix } func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options entities.PlayKubeOptions) (_ *entities.PlayKubeReport, finalErr error) { @@ -132,7 +132,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options // TODO: create constants for the various "kinds" of yaml files. var serviceContainer *libpod.Container if options.ServiceContainer && (kind == "Pod" || kind == "Deployment") { - ctr, err := ic.createServiceContainer(ctx, serviceContainerName(content), options) + ctr, err := ic.createServiceContainer(ctx, k8sName(content, "service"), options) if err != nil { return nil, err } @@ -213,6 +213,19 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options return nil, fmt.Errorf("unable to read YAML as Kube ConfigMap: %w", err) } configMaps = append(configMaps, configMap) + case "Secret": + var secret v1.Secret + + if err := yaml.Unmarshal(document, &secret); err != nil { + return nil, fmt.Errorf("unable to read YAML as kube secret: %w", err) + } + + r, err := ic.playKubeSecret(&secret) + if err != nil { + return nil, err + } + report.Secrets = append(report.Secrets, entities.PlaySecret{CreateReport: r}) + validKinds++ default: logrus.Infof("Kube kind %s not supported", kind) continue @@ -380,7 +393,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY configMaps = append(configMaps, cm) } - volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps) + volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps, secretsManager) if err != nil { return nil, err } @@ -388,7 +401,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // Go through the volumes and create a podman volume for all volumes that have been // defined by a configmap for _, v := range volumes { - if v.Type == kube.KubeVolumeTypeConfigMap && !v.Optional { + if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional { vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source)) if err != nil { if errors.Is(err, define.ErrVolumeExists) { @@ -583,6 +596,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY UserNSIsHost: p.Userns.IsHost(), Volumes: volumes, } + specGen, err := kube.ToSpecGen(ctx, &specgenOpts) if err != nil { return nil, err @@ -968,3 +982,55 @@ func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, _ e return reports, nil } + +// playKubeSecret allows users to create and store a kubernetes secret as a podman secret +func (ic *ContainerEngine) playKubeSecret(secret *v1.Secret) (*entities.SecretCreateReport, error) { + r := &entities.SecretCreateReport{} + + // Create the secret manager before hand + secretsManager, err := ic.Libpod.SecretsManager() + if err != nil { + return nil, err + } + + data, err := yaml.Marshal(secret) + if err != nil { + return nil, err + } + + secretsPath := ic.Libpod.GetSecretsStorageDir() + opts := make(map[string]string) + opts["path"] = filepath.Join(secretsPath, "filedriver") + // maybe k8sName(data)... + // using this does not allow the user to use the name given to the secret + // but keeping secret.Name as the ID can lead to a collision. + + s, err := secretsManager.Lookup(secret.Name) + if err == nil { + if val, ok := s.Metadata["immutable"]; ok { + if val == "true" { + return nil, fmt.Errorf("cannot remove colliding secret as it is set to immutable") + } + } + _, err = secretsManager.Delete(s.Name) + if err != nil { + return nil, err + } + } + + // now we have either removed the old secret w/ the same name or + // the name was not taken. Either way, we can now store. + + meta := make(map[string]string) + if secret.Immutable != nil && *secret.Immutable { + meta["immutable"] = "true" + } + secretID, err := secretsManager.Store(secret.Name, data, "file", opts, meta) + if err != nil { + return nil, err + } + + r.ID = secretID + + return r, nil +} diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go index 7321ef715..e82fa4fdd 100644 --- a/pkg/domain/infra/abi/secrets.go +++ b/pkg/domain/infra/abi/secrets.go @@ -42,7 +42,7 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader } } - secretID, err := manager.Store(name, data, options.Driver, options.DriverOpts) + secretID, err := manager.Store(name, data, options.Driver, options.DriverOpts, nil) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index fcabff7c4..98c73c51a 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -57,10 +57,14 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin } func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { - ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) + ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, options.All, false, namesOrIds, options.Filters) if err != nil { return nil, err } + ctrMap := map[string]string{} + for i := range ctrs { + ctrMap[ctrs[i].ID] = rawInputs[i] + } reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.Pause(ic.ClientCtx, c.ID, nil) @@ -68,24 +72,36 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri logrus.Debugf("Container %s is not running", c.ID) continue } - reports = append(reports, &entities.PauseUnpauseReport{Id: c.ID, Err: err}) + reports = append(reports, &entities.PauseUnpauseReport{ + Id: c.ID, + Err: err, + RawInput: ctrMap[c.ID], + }) } return reports, nil } func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { - reports := []*entities.PauseUnpauseReport{} - ctrs, err := getContainersByContext(ic.ClientCtx, options.All, false, namesOrIds) + ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, options.All, false, namesOrIds, options.Filters) if err != nil { return nil, err } + ctrMap := map[string]string{} + for i := range ctrs { + ctrMap[ctrs[i].ID] = rawInputs[i] + } + reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.Unpause(ic.ClientCtx, c.ID, nil) if err != nil && options.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not paused", c.ID) continue } - reports = append(reports, &entities.PauseUnpauseReport{Id: c.ID, Err: err}) + reports = append(reports, &entities.PauseUnpauseReport{ + Id: c.ID, + Err: err, + RawInput: ctrMap[c.ID], + }) } return reports, nil } @@ -771,8 +787,17 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta for _, w := range con.Warnings { fmt.Fprintf(os.Stderr, "%s\n", w) } + removeContainer := func(id string, force bool) error { + removeOptions := new(containers.RemoveOptions).WithVolumes(true).WithForce(force) + reports, err := containers.Remove(ic.ClientCtx, id, removeOptions) + logIfRmError(id, err, reports) + return err + } + if opts.CIDFile != "" { if err := util.CreateCidFile(opts.CIDFile, con.ID); err != nil { + // If you fail to create CIDFile then remove the container + _ = removeContainer(con.ID, true) return nil, err } } @@ -784,6 +809,11 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta err := containers.Start(ic.ClientCtx, con.ID, new(containers.StartOptions).WithRecursive(true)) if err != nil { report.ExitCode = define.ExitCode(err) + if opts.Rm { + if rmErr := removeContainer(con.ID, true); rmErr != nil && !errors.Is(rmErr, define.ErrNoSuchCtr) { + logrus.Errorf("Container %s failed to be removed", con.ID) + } + } } return &report, err } @@ -796,10 +826,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta report.ExitCode = define.ExitCode(err) if opts.Rm { - reports, rmErr := containers.Remove(ic.ClientCtx, con.ID, new(containers.RemoveOptions).WithForce(false).WithVolumes(true)) - if rmErr != nil || reports[0].Err != nil { - logrus.Debugf("unable to remove container %s after failing to start and attach to it", con.ID) - } + _ = removeContainer(con.ID, false) } return &report, err } @@ -815,8 +842,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } if !shouldRestart { - reports, err := containers.Remove(ic.ClientCtx, con.ID, new(containers.RemoveOptions).WithForce(false).WithVolumes(true)) - logIfRmError(con.ID, err, reports) + _ = removeContainer(con.ID, false) } }() } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 18f750dcc..9ad408850 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -240,7 +240,7 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, opts entities.ImagePushOptions) error { options := new(images.PushOptions) - options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures) + options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures).WithQuiet(opts.Quiet) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s == types.OptionalBoolTrue { diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index d2554f198..4a3148fac 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -110,5 +110,15 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin } } digest, err := manifests.Push(ir.ClientCtx, name, destination, options) + if err != nil { + return "", fmt.Errorf("error adding to manifest list %s: %w", name, err) + } + + if opts.Rm { + if _, rmErrors := ir.Remove(ctx, []string{name}, entities.ImageRemoveOptions{LookupManifest: true}); len(rmErrors) > 0 { + return "", fmt.Errorf("error removing manifest after push: %w", rmErrors[0]) + } + } + return digest, err } diff --git a/pkg/k8s.io/api/core/v1/types.go b/pkg/k8s.io/api/core/v1/types.go index 48f353cc6..39a675dae 100644 --- a/pkg/k8s.io/api/core/v1/types.go +++ b/pkg/k8s.io/api/core/v1/types.go @@ -56,6 +56,7 @@ type VolumeSource struct { // ConfigMap represents a configMap that should populate this volume // +optional ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"` + Secret *SecretVolumeSource } // PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. diff --git a/pkg/machine/e2e/basic_test.go b/pkg/machine/e2e/basic_test.go index f67fb4c67..da0310485 100644 --- a/pkg/machine/e2e/basic_test.go +++ b/pkg/machine/e2e/basic_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( . "github.com/onsi/ginkgo" @@ -20,7 +20,7 @@ var _ = Describe("run basic podman commands", func() { }) It("Basic ops", func() { - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run() Expect(err).To(BeNil()) diff --git a/pkg/machine/e2e/config_basic.go b/pkg/machine/e2e/config_basic_test.go index be0896156..d1cb24174 100644 --- a/pkg/machine/e2e/config_basic.go +++ b/pkg/machine/e2e/config_basic_test.go @@ -1,8 +1,7 @@ -package e2e +package e2e_test type basicMachine struct { args []string - cmd []string } func (s basicMachine) buildCmd(m *machineTestBuilder) []string { diff --git a/pkg/machine/e2e/config_info.go b/pkg/machine/e2e/config_info_test.go index 410c7e518..4da40ab99 100644 --- a/pkg/machine/e2e/config_info.go +++ b/pkg/machine/e2e/config_info_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test type infoMachine struct { format string diff --git a/pkg/machine/e2e/config_init.go b/pkg/machine/e2e/config_init_test.go index 7f18cce7d..d6c7990b0 100644 --- a/pkg/machine/e2e/config_init.go +++ b/pkg/machine/e2e/config_init_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "strconv" @@ -25,7 +25,7 @@ type initMachine struct { memory *uint now bool timezone string - rootful bool + rootful bool //nolint:unused,structcheck volumes []string cmd []string @@ -71,7 +71,7 @@ func (i *initMachine) withDiskSize(size uint) *initMachine { return i } -func (i *initMachine) withIgnitionPath(path string) *initMachine { +func (i *initMachine) withIgnitionPath(path string) *initMachine { //nolint:unused i.ignitionPath = path return i } diff --git a/pkg/machine/e2e/config_inspect.go b/pkg/machine/e2e/config_inspect_test.go index 74c9a5d9c..ffd74220f 100644 --- a/pkg/machine/e2e/config_inspect.go +++ b/pkg/machine/e2e/config_inspect_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test type inspectMachine struct { /* diff --git a/pkg/machine/e2e/config_list.go b/pkg/machine/e2e/config_list_test.go index 150f984bc..78f9edc62 100644 --- a/pkg/machine/e2e/config_list.go +++ b/pkg/machine/e2e/config_list_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test type listMachine struct { /* diff --git a/pkg/machine/e2e/config_rm.go b/pkg/machine/e2e/config_rm_test.go index 6cf262a22..1f9c9b4ec 100644 --- a/pkg/machine/e2e/config_rm.go +++ b/pkg/machine/e2e/config_rm_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test type rmMachine struct { /* @@ -40,17 +40,17 @@ func (i *rmMachine) withForce() *rmMachine { return i } -func (i *rmMachine) withSaveIgnition() *rmMachine { +func (i *rmMachine) withSaveIgnition() *rmMachine { //nolint:unused i.saveIgnition = true return i } -func (i *rmMachine) withSaveImage() *rmMachine { +func (i *rmMachine) withSaveImage() *rmMachine { //nolint:unused i.saveImage = true return i } -func (i *rmMachine) withSaveKeys() *rmMachine { +func (i *rmMachine) withSaveKeys() *rmMachine { //nolint:unused i.saveKeys = true return i } diff --git a/pkg/machine/e2e/config_set.go b/pkg/machine/e2e/config_set_test.go index b310ab1b9..3c773b970 100644 --- a/pkg/machine/e2e/config_set.go +++ b/pkg/machine/e2e/config_set_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "strconv" diff --git a/pkg/machine/e2e/config_ssh.go b/pkg/machine/e2e/config_ssh_test.go index b09eed47d..f062625fa 100644 --- a/pkg/machine/e2e/config_ssh.go +++ b/pkg/machine/e2e/config_ssh_test.go @@ -1,14 +1,12 @@ -package e2e +package e2e_test type sshMachine struct { /* --username string Username to use when ssh-ing into the VM. */ - username string + username string //nolint:unused sshCommand []string - - cmd []string } func (s sshMachine) buildCmd(m *machineTestBuilder) []string { @@ -22,7 +20,7 @@ func (s sshMachine) buildCmd(m *machineTestBuilder) []string { return cmd } -func (s *sshMachine) withUsername(name string) *sshMachine { +func (s *sshMachine) withUsername(name string) *sshMachine { //nolint:unused s.username = name return s } diff --git a/pkg/machine/e2e/config_start.go b/pkg/machine/e2e/config_start_test.go index 86b1721f8..d9efbf489 100644 --- a/pkg/machine/e2e/config_start.go +++ b/pkg/machine/e2e/config_start_test.go @@ -1,10 +1,9 @@ -package e2e +package e2e_test type startMachine struct { /* No command line args other than a machine vm name (also not required) */ - cmd []string } func (s startMachine) buildCmd(m *machineTestBuilder) []string { diff --git a/pkg/machine/e2e/config_stop.go b/pkg/machine/e2e/config_stop_test.go index 04dcfb524..41142ec7e 100644 --- a/pkg/machine/e2e/config_stop.go +++ b/pkg/machine/e2e/config_stop_test.go @@ -1,10 +1,9 @@ -package e2e +package e2e_test type stopMachine struct { /* No command line args other than a machine vm name (also not required) */ - cmd []string } func (s stopMachine) buildCmd(m *machineTestBuilder) []string { diff --git a/pkg/machine/e2e/config.go b/pkg/machine/e2e/config_test.go index b3fe74b0c..9940e711b 100644 --- a/pkg/machine/e2e/config.go +++ b/pkg/machine/e2e/config_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "encoding/json" @@ -10,13 +10,11 @@ import ( "time" "github.com/containers/podman/v4/pkg/machine" - "github.com/containers/podman/v4/pkg/machine/qemu" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage/pkg/stringid" - . "github.com/onsi/ginkgo" //nolint:golint,stylecheck + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" - . "github.com/onsi/gomega/gexec" //nolint:golint,stylecheck + . "github.com/onsi/gomega/gexec" ) var originalHomeDir = os.Getenv("HOME") @@ -36,7 +34,7 @@ type MachineTestBuilder interface { run() (*machineSession, error) } type machineSession struct { - *gexec.Session + *Session } type machineTestBuilder struct { @@ -47,10 +45,6 @@ type machineTestBuilder struct { podmanBinary string timeout time.Duration } -type qemuMachineInspectInfo struct { - State machine.Status - VM qemu.MachineVM -} // waitWithTimeout waits for a command to complete for a given // number of seconds @@ -121,7 +115,7 @@ func (m *machineTestBuilder) setCmd(mc machineCommand) *machineTestBuilder { // If no name for the machine exists, we set a random name. if !util.StringInSlice(m.name, m.names) { if len(m.name) < 1 { - m.name = randomString(12) + m.name = randomString() } m.names = append(m.names, m.name) } @@ -136,10 +130,10 @@ func (m *machineTestBuilder) setTimeout(timeout time.Duration) *machineTestBuild // toQemuInspectInfo is only for inspecting qemu machines. Other providers will need // to make their own. -func (mb *machineTestBuilder) toQemuInspectInfo() ([]machine.InspectInfo, int, error) { +func (m *machineTestBuilder) toQemuInspectInfo() ([]machine.InspectInfo, int, error) { args := []string{"machine", "inspect"} - args = append(args, mb.names...) - session, err := runWrapper(mb.podmanBinary, args, defaultTimeout, true) + args = append(args, m.names...) + session, err := runWrapper(m.podmanBinary, args, defaultTimeout, true) if err != nil { return nil, -1, err } @@ -175,9 +169,7 @@ func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration, wa return &ms, nil } -func (m *machineTestBuilder) init() {} - // randomString returns a string of given length composed of random characters -func randomString(n int) string { +func randomString() string { return stringid.GenerateRandomID()[0:12] } diff --git a/pkg/machine/e2e/info_test.go b/pkg/machine/e2e/info_test.go index eeabb78af..fe0cfba32 100644 --- a/pkg/machine/e2e/info_test.go +++ b/pkg/machine/e2e/info_test.go @@ -1,7 +1,7 @@ -package e2e +package e2e_test import ( - "github.com/containers/podman/v4/cmd/podman/machine" + "github.com/containers/podman/v4/pkg/domain/entities" jsoniter "github.com/json-iterator/go" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -51,7 +51,7 @@ var _ = Describe("podman machine info", func() { Expect(err).NotTo(HaveOccurred()) Expect(infoSession).Should(Exit(0)) - infoReport := &machine.Info{} + infoReport := &entities.MachineInfo{} err = jsoniter.Unmarshal(infoSession.Bytes(), infoReport) Expect(err).To(BeNil()) }) diff --git a/pkg/machine/e2e/init_test.go b/pkg/machine/e2e/init_test.go index 40f140cae..b246dc4da 100644 --- a/pkg/machine/e2e/init_test.go +++ b/pkg/machine/e2e/init_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "io/ioutil" @@ -78,7 +78,7 @@ var _ = Describe("podman machine init", func() { }) It("machine init with cpus, disk size, memory, timezone", func() { - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withCPUs(2).withDiskSize(102).withMemory(4000).withTimezone("Pacific/Honolulu")).run() Expect(err).To(BeNil()) @@ -108,7 +108,7 @@ var _ = Describe("podman machine init", func() { switch runtime.GOOS { // os's handle memory differently case "linux": - Expect(memorySession.outputToString()).To(ContainSubstring("3821")) + Expect(memorySession.outputToString()).To(ContainSubstring("3822")) case "darwin": Expect(memorySession.outputToString()).To(ContainSubstring("3824")) default: @@ -130,7 +130,7 @@ var _ = Describe("podman machine init", func() { mount := tmpDir + ":/testmountdir" defer os.RemoveAll(tmpDir) - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withVolume(mount)).run() Expect(err).To(BeNil()) diff --git a/pkg/machine/e2e/inspect_test.go b/pkg/machine/e2e/inspect_test.go index 93fb8cc2b..0ab928205 100644 --- a/pkg/machine/e2e/inspect_test.go +++ b/pkg/machine/e2e/inspect_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "strings" @@ -52,15 +52,15 @@ var _ = Describe("podman machine stop", func() { }) It("inspect with go format", func() { - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run() Expect(err).To(BeNil()) Expect(session).To(Exit(0)) // regular inspect should - inspectJson := new(inspectMachine) - inspectSession, err := mb.setName(name).setCmd(inspectJson).run() + inspectJSON := new(inspectMachine) + inspectSession, err := mb.setName(name).setCmd(inspectJSON).run() Expect(err).To(BeNil()) Expect(inspectSession).To(Exit(0)) diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go index 8b7443d47..5c7ae6c5e 100644 --- a/pkg/machine/e2e/list_test.go +++ b/pkg/machine/e2e/list_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "strings" @@ -45,8 +45,8 @@ var _ = Describe("podman machine list", func() { It("list machines with quiet or noheading", func() { // Random names for machines to test list - name1 := randomString(12) - name2 := randomString(12) + name1 := randomString() + name2 := randomString() list := new(listMachine) firstList, err := mb.setCmd(list.withQuiet()).run() @@ -109,7 +109,7 @@ var _ = Describe("podman machine list", func() { It("list with --format", func() { // Random names for machines to test list - name1 := randomString(12) + name1 := randomString() i := new(initMachine) session, err := mb.setName(name1).setCmd(i.withImagePath(mb.imagePath)).run() diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go index 93eabdad3..5de04b9f7 100644 --- a/pkg/machine/e2e/machine_test.go +++ b/pkg/machine/e2e/machine_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "fmt" diff --git a/pkg/machine/e2e/rm_test.go b/pkg/machine/e2e/rm_test.go index 43b8c594c..e33eaf702 100644 --- a/pkg/machine/e2e/rm_test.go +++ b/pkg/machine/e2e/rm_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( . "github.com/onsi/ginkgo" diff --git a/pkg/machine/e2e/set_test.go b/pkg/machine/e2e/set_test.go index 80cb89488..4839e33da 100644 --- a/pkg/machine/e2e/set_test.go +++ b/pkg/machine/e2e/set_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "runtime" @@ -22,7 +22,7 @@ var _ = Describe("podman machine set", func() { }) It("set machine cpus, disk, memory", func() { - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run() Expect(err).To(BeNil()) @@ -62,7 +62,7 @@ var _ = Describe("podman machine set", func() { switch runtime.GOOS { // it seems macos and linux handle memory differently case "linux": - Expect(memorySession.outputToString()).To(ContainSubstring("3821")) + Expect(memorySession.outputToString()).To(ContainSubstring("3822")) case "darwin": Expect(memorySession.outputToString()).To(ContainSubstring("3824")) default: @@ -75,7 +75,7 @@ var _ = Describe("podman machine set", func() { }) It("no settings should change if no flags", func() { - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run() Expect(err).To(BeNil()) diff --git a/pkg/machine/e2e/ssh_test.go b/pkg/machine/e2e/ssh_test.go index 9ee31ac26..52d714c91 100644 --- a/pkg/machine/e2e/ssh_test.go +++ b/pkg/machine/e2e/ssh_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( . "github.com/onsi/ginkgo" @@ -20,17 +20,16 @@ var _ = Describe("podman machine ssh", func() { }) It("bad machine name", func() { - name := randomString(12) + name := randomString() ssh := sshMachine{} session, err := mb.setName(name).setCmd(ssh).run() Expect(err).To(BeNil()) Expect(session).To(Exit(125)) - // TODO seems like stderr is not being returned; re-enabled when fixed - //Expect(session.outputToString()).To(ContainSubstring("not exist")) + Expect(session.errorToString()).To(ContainSubstring("not exist")) }) It("ssh to non-running machine", func() { - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run() Expect(err).To(BeNil()) @@ -39,13 +38,12 @@ var _ = Describe("podman machine ssh", func() { ssh := sshMachine{} sshSession, err := mb.setName(name).setCmd(ssh).run() Expect(err).To(BeNil()) - // TODO seems like stderr is not being returned; re-enabled when fixed - //Expect(sshSession.outputToString()).To(ContainSubstring("is not running")) + Expect(sshSession.errorToString()).To(ContainSubstring("is not running")) Expect(sshSession).To(Exit(125)) }) It("ssh to running machine and check os-type", func() { - name := randomString(12) + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run() Expect(err).To(BeNil()) diff --git a/pkg/machine/e2e/start_test.go b/pkg/machine/e2e/start_test.go index 1de66eb9a..1f9405569 100644 --- a/pkg/machine/e2e/start_test.go +++ b/pkg/machine/e2e/start_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( "github.com/containers/podman/v4/pkg/machine" diff --git a/pkg/machine/e2e/stop_test.go b/pkg/machine/e2e/stop_test.go index 0c27045a6..621bbdb16 100644 --- a/pkg/machine/e2e/stop_test.go +++ b/pkg/machine/e2e/stop_test.go @@ -1,4 +1,4 @@ -package e2e +package e2e_test import ( . "github.com/onsi/ginkgo" diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 3b57455c4..7974c261e 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -670,11 +670,11 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { // because / is immutable, we have to monkey around with permissions // if we dont mount in /home or /mnt args := []string{"-q", "--"} - if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") { + if !strings.HasPrefix(mount.Target, "/home") && !strings.HasPrefix(mount.Target, "/mnt") { args = append(args, "sudo", "chattr", "-i", "/", ";") } args = append(args, "sudo", "mkdir", "-p", mount.Target) - if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") { + if !strings.HasPrefix(mount.Target, "/home") && !strings.HasPrefix(mount.Target, "/mnt") { args = append(args, ";", "sudo", "chattr", "+i", "/", ";") } err = v.SSH(name, machine.SSHOptions{Args: args}) diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 2248c9235..8cfac924b 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -302,60 +302,6 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat return warnings, nil } -// FinishThrottleDevices takes the temporary representation of the throttle -// devices in the specgen and looks up the major and major minors. it then -// sets the throttle devices proper in the specgen -func FinishThrottleDevices(s *specgen.SpecGenerator) error { - if bps := s.ThrottleReadBpsDevice; len(bps) > 0 { - for k, v := range bps { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return err - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - if s.ResourceLimits.BlockIO == nil { - s.ResourceLimits.BlockIO = new(spec.LinuxBlockIO) - } - s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v) - } - } - if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 { - for k, v := range bps { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return err - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v) - } - } - if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 { - for k, v := range iops { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return err - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v) - } - } - if iops := s.ThrottleWriteIOPSDevice; len(iops) > 0 { - for k, v := range iops { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return err - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v) - } - } - return nil -} - // ConfigToSpec takes a completed container config and converts it back into a specgenerator for purposes of cloning an existing container func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID string) (*libpod.Container, *libpod.InfraInherit, error) { c, err := rt.LookupContainer(contaierID) @@ -540,3 +486,72 @@ func mapSecurityConfig(c *libpod.ContainerConfig, s *specgen.SpecGenerator) { s.Groups = c.Groups s.HostUsers = c.HostUsers } + +// FinishThrottleDevices takes the temporary representation of the throttle +// devices in the specgen and looks up the major and major minors. it then +// sets the throttle devices proper in the specgen +func FinishThrottleDevices(s *specgen.SpecGenerator) error { + if s.ResourceLimits == nil { + s.ResourceLimits = &spec.LinuxResources{} + } + if bps := s.ThrottleReadBpsDevice; len(bps) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range bps { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = new(spec.LinuxBlockIO) + } + s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v) + } + } + if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range bps { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v) + } + } + if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range iops { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v) + } + } + if iops := s.ThrottleWriteIOPSDevice; len(iops) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range iops { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v) + } + } + return nil +} diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 51d290bb4..8334d386f 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -58,6 +58,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener if err := FinishThrottleDevices(s); err != nil { return nil, nil, nil, err } + // Set defaults for unset namespaces if s.PidNS.IsDefault() { defaultNS, err := GetDefaultNamespaceMode("pid", rtc, pod) diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 454a1e1d0..e9abf419b 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -398,6 +398,16 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener Type: "b", } s.Devices = append(s.Devices, device) + case KubeVolumeTypeSecret: + // in podman play kube we need to add these secrets as volumes rather than as + // specgen.Secrets. Adding them as volumes allows for all key: value pairs to be mounted + secretVolume := specgen.NamedVolume{ + Dest: volume.MountPath, + Name: volumeSource.Source, + Options: options, + } + + s.Volumes = append(s.Volumes, &secretVolume) default: return nil, errors.New("unsupported volume source type") } diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go index 466dab610..470c0c39c 100644 --- a/pkg/specgen/generate/kube/play_test.go +++ b/pkg/specgen/generate/kube/play_test.go @@ -28,7 +28,7 @@ func createSecrets(t *testing.T, d string) *secrets.SecretsManager { data, err := json.Marshal(s.Data) assert.NoError(t, err) - _, err = secretsManager.Store(s.ObjectMeta.Name, data, driver, driverOpts) + _, err = secretsManager.Store(s.ObjectMeta.Name, data, driver, driverOpts, nil) assert.NoError(t, err) } diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index f5c0c241d..c12adadd8 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -6,9 +6,13 @@ import ( "os" "github.com/containers/common/pkg/parse" + "github.com/containers/common/pkg/secrets" "github.com/containers/podman/v4/libpod" v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1" + metav1 "github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" ) const ( @@ -27,6 +31,7 @@ const ( KubeVolumeTypeConfigMap KubeVolumeTypeBlockDevice KubeVolumeTypeCharDevice + KubeVolumeTypeSecret ) //nolint:revive @@ -125,6 +130,49 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) }, nil } +// VolumeFromSecret creates a new kube volume from a kube secret. +func VolumeFromSecret(secretSource *v1.SecretVolumeSource, secretsManager *secrets.SecretsManager) (*KubeVolume, error) { + // returns a byte array of a kube secret data, meaning this needs to go into a string map + _, secretByte, err := secretsManager.LookupSecretData(secretSource.SecretName) + if err != nil { + return nil, err + } + + // unmarshaling directly into a v1.secret creates type mismatch errors + // use a more friendly, string only secret struct. + type KubeSecret struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + // +optional + Immutable *bool `json:"immutable,omitempty"` + Data map[string]string `json:"data,omitempty"` + // +optional + StringData map[string]string `json:"stringData,omitempty"` + // +optional + Type string `json:"type,omitempty"` + } + + data := &KubeSecret{} + + err = yaml.Unmarshal(secretByte, data) + if err != nil { + return nil, err + } + + kv := &KubeVolume{} + kv.Type = KubeVolumeTypeSecret + kv.Source = secretSource.SecretName + kv.Optional = *secretSource.Optional + kv.Items = make(map[string]string) + + // add key: value pairs to the items array + for key, entry := range data.Data { + kv.Items[key] = entry + } + return kv, nil +} + // Create a KubeVolume from a PersistentVolumeClaimVolumeSource func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource) (*KubeVolume, error) { return &KubeVolume{ @@ -172,7 +220,7 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config } // Create a KubeVolume from one of the supported VolumeSource -func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) { +func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (*KubeVolume, error) { switch { case volumeSource.HostPath != nil: return VolumeFromHostPath(volumeSource.HostPath) @@ -180,17 +228,19 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap) ( return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim) case volumeSource.ConfigMap != nil: return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps) + case volumeSource.Secret != nil: + return VolumeFromSecret(volumeSource.Secret, secretsManager) default: return nil, errors.New("HostPath, ConfigMap, and PersistentVolumeClaim are currently the only supported VolumeSource") } } // Create a map of volume name to KubeVolume -func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap) (map[string]*KubeVolume, error) { +func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (map[string]*KubeVolume, error) { volumes := make(map[string]*KubeVolume) for _, specVolume := range specVolumes { - volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps) + volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager) if err != nil { return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err) } diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index bb5f2d0ec..f59fe1011 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -309,6 +309,17 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } g.Config.Linux.Resources = s.ResourceLimits } + + weightDevices, err := WeightDevices(s.WeightDevice) + if err != nil { + return nil, err + } + if len(weightDevices) > 0 { + for _, dev := range weightDevices { + g.AddLinuxResourcesBlockIOWeightDevice(dev.Major, dev.Minor, *dev.Weight) + } + } + // Devices // set the default rule at the beginning of device configuration if !inUserNS && !s.Privileged { @@ -345,14 +356,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } } - for k, v := range s.WeightDevice { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return nil, fmt.Errorf("failed to inspect '%s' in --blkio-weight-device: %w", k, err) - } - g.AddLinuxResourcesBlockIOWeightDevice((int64(unix.Major(uint64(statT.Rdev)))), (int64(unix.Minor(uint64(statT.Rdev)))), *v.Weight) //nolint: unconvert - } - BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g) g.ClearProcessEnv() @@ -413,3 +416,19 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt return configSpec, nil } + +func WeightDevices(wtDevices map[string]spec.LinuxWeightDevice) ([]spec.LinuxWeightDevice, error) { + devs := []spec.LinuxWeightDevice{} + for k, v := range wtDevices { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return nil, fmt.Errorf("failed to inspect '%s' in --blkio-weight-device: %w", k, err) + } + dev := new(spec.LinuxWeightDevice) + dev.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + dev.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + dev.Weight = v.Weight + devs = append(devs, *dev) + } + return devs, nil +} diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 212d613fe..4e6362c9b 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -13,6 +13,7 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/specgenutil" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -21,6 +22,10 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) { return nil, err } + if p.PodSpecGen.ResourceLimits == nil { + p.PodSpecGen.ResourceLimits = &specs.LinuxResources{} + } + if !p.PodSpecGen.NoInfra { imageName, err := PullOrBuildInfraImage(rt, p.PodSpecGen.InfraImage) if err != nil { @@ -38,10 +43,33 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) { } } + if !p.PodSpecGen.NoInfra { + err := FinishThrottleDevices(p.PodSpecGen.InfraContainerSpec) + if err != nil { + return nil, err + } + if p.PodSpecGen.InfraContainerSpec.ResourceLimits.BlockIO != nil { + p.PodSpecGen.ResourceLimits.BlockIO = p.PodSpecGen.InfraContainerSpec.ResourceLimits.BlockIO + } + + weightDevices, err := WeightDevices(p.PodSpecGen.InfraContainerSpec.WeightDevice) + if err != nil { + return nil, err + } + + if p.PodSpecGen.ResourceLimits != nil && len(weightDevices) > 0 { + if p.PodSpecGen.ResourceLimits.BlockIO == nil { + p.PodSpecGen.ResourceLimits.BlockIO = &specs.LinuxBlockIO{} + } + p.PodSpecGen.ResourceLimits.BlockIO.WeightDevice = weightDevices + } + } + options, err := createPodOptions(&p.PodSpecGen) if err != nil { return nil, err } + pod, err := rt.NewPod(context.Background(), p.PodSpecGen, options...) if err != nil { return nil, err @@ -55,6 +83,11 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) { return nil, err } p.PodSpecGen.InfraContainerSpec.User = "" // infraSpec user will get incorrectly assigned via the container creation process, overwrite here + // infra's resource limits are used as a parsing tool, + // we do not want infra to get these resources in its cgroup + // make sure of that here. + p.PodSpecGen.InfraContainerSpec.ResourceLimits = nil + p.PodSpecGen.InfraContainerSpec.WeightDevice = nil rtSpec, spec, opts, err := MakeContainer(context.Background(), rt, p.PodSpecGen.InfraContainerSpec, false, nil) if err != nil { return nil, err @@ -122,6 +155,10 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er options = append(options, libpod.WithPodHostname(p.Hostname)) } + if p.ResourceLimits != nil { + options = append(options, libpod.WithPodResources(*p.ResourceLimits)) + } + options = append(options, libpod.WithPodExitPolicy(p.ExitPolicy)) return options, nil diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 9a7d50947..7392e7b44 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -74,14 +74,21 @@ func getCPULimits(c *entities.ContainerCreateOptions) *specs.LinuxCPU { func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxBlockIO, error) { var err error io := &specs.LinuxBlockIO{} + if s.ResourceLimits == nil { + s.ResourceLimits = &specs.LinuxResources{} + } hasLimits := false if b := c.BlkIOWeight; len(b) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &specs.LinuxBlockIO{} + } u, err := strconv.ParseUint(b, 10, 16) if err != nil { return nil, fmt.Errorf("invalid value for blkio-weight: %w", err) } nu := uint16(u) io.Weight = &nu + s.ResourceLimits.BlockIO.Weight = &nu hasLimits = true } @@ -509,7 +516,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions return err } } - if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0) { + if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0 || len(c.DeviceReadBPs) != 0 || len(c.DeviceWriteBPs) != 0) { s.ResourceLimits.BlockIO, err = getIOLimits(s, c) if err != nil { return err diff --git a/pkg/util/filters.go b/pkg/util/filters.go index 08148806f..104b9c3c2 100644 --- a/pkg/util/filters.go +++ b/pkg/util/filters.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "path/filepath" "strings" "time" @@ -94,35 +93,3 @@ func PrepareFilters(r *http.Request) (*map[string][]string, error) { } return &filterMap, nil } - -func matchPattern(pattern string, value string) bool { - if strings.Contains(pattern, "*") { - filter := fmt.Sprintf("*%s*", pattern) - filter = strings.ReplaceAll(filter, string(filepath.Separator), "|") - newName := strings.ReplaceAll(value, string(filepath.Separator), "|") - match, _ := filepath.Match(filter, newName) - return match - } - return false -} - -// MatchLabelFilters matches labels and returns true if they are valid -func MatchLabelFilters(filterValues []string, labels map[string]string) bool { -outer: - for _, filterValue := range filterValues { - filterArray := strings.SplitN(filterValue, "=", 2) - filterKey := filterArray[0] - if len(filterArray) > 1 { - filterValue = filterArray[1] - } else { - filterValue = "" - } - for labelKey, labelValue := range labels { - if ((labelKey == filterKey) || matchPattern(filterKey, labelKey)) && (filterValue == "" || labelValue == filterValue) { - continue outer - } - } - return false - } - return true -} diff --git a/pkg/util/filters_test.go b/pkg/util/filters_test.go index 47259013e..8e45ea61c 100644 --- a/pkg/util/filters_test.go +++ b/pkg/util/filters_test.go @@ -2,6 +2,8 @@ package util import ( "testing" + + "github.com/containers/common/pkg/filters" ) func TestMatchLabelFilters(t *testing.T) { @@ -71,7 +73,7 @@ func TestMatchLabelFilters(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - if got := MatchLabelFilters(tt.args.filterValues, tt.args.labels); got != tt.want { + if got := filters.MatchLabelFilters(tt.args.filterValues, tt.args.labels); got != tt.want { t.Errorf("MatchLabelFilters() = %v, want %v", got, tt.want) } }) |