diff options
39 files changed, 1184 insertions, 130 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 5fefbacdf..1121806d5 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -36,7 +36,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, createFlags.StringSliceVar( &cf.Annotation, annotationFlagName, []string{}, - "Add annotations to container (key:value)", + "Add annotations to container (key=value)", ) _ = cmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone) diff --git a/contrib/helloimage/Containerfile b/contrib/helloimage/Containerfile index bea71cae0..0cbf6d9a0 100644 --- a/contrib/helloimage/Containerfile +++ b/contrib/helloimage/Containerfile @@ -1,8 +1,11 @@ -FROM registry.access.redhat.com/ubi8-micro:latest +FROM docker.io/alpine as builder +RUN apk add gcc libc-dev +ADD podman_hello_world.c . +RUN gcc -O2 -static -o podman_hello_world podman_hello_world.c +FROM scratch LABEL maintainer="Podman Maintainers" LABEL artist="Máirín Ní Ḋuḃṫaiġ, Twitter:@mairin" -WORKDIR /tmp - -COPY podman_hello_world.bash . -ENTRYPOINT ./podman_hello_world.bash +USER 1000 +COPY --from=builder podman_hello_world /usr/local/bin/podman_hello_world +CMD ["/usr/local/bin/podman_hello_world"] diff --git a/contrib/helloimage/README.md b/contrib/helloimage/README.md index 93edcc527..ca69f87b4 100644 --- a/contrib/helloimage/README.md +++ b/contrib/helloimage/README.md @@ -19,7 +19,7 @@ Using this image is helpful to: The contents of this directory contain: * ./Containerfile - * ./podman_hello_world.bash + * ./podman_hello_world.c ## Sample Usage @@ -28,7 +28,7 @@ To simply run the image: ``` podman run quay.io/podman/hello -! ... Hello Podman World ...! +!... Hello Podman World ...! .--"--. / - - \ @@ -49,7 +49,29 @@ To build the image yourself, copy the files from this directory into a local directory and issue these commands: ``` -chmod 755 ./podman_hello_world.bash podman build -t myhello . podman run myhello ``` + +## Potential Issues: + +The image runs as a rootless user with the UID set to `1000`. +If the /etc/subuid and /etch/subgid values are not set appropriately to run as a +rootless user on the host, an error like this might be raised: + +``` +Copying blob acab339ca1e8 done +ERRO[0002] Error while applying layer: ApplyLayer exit status 1 stdout: stderr: potentially insufficient UIDs or GIDs available in user namespace (requested 0:12 for /var/spool/mail): Check /etc/subuid and /etc/subgid: lchown /var/spool/mail: invalid argument +Error: writing blob: adding layer with blob "sha256:ee0cde9de8a68f171a8c03b0e9954abf18576947e2f3187e84d8c31ccd8f6a09": ApplyLayer exit status 1 stdout: stderr: potentially insufficient UIDs or GIDs available in user namespace (requested 0:12 for /var/spool/mail): Check /etc/subuid and /etc/subgid: lchown /var/spool/mail: invalid argument +``` + +Please refer to this [blog post](https://www.redhat.com/sysadmin/rootless-podman) for further configuration information. + +## THANKS! + +Many Thanks to @afbjorklund for a great discussion during the +first revision of this container image that resulted in moving +from using bash to using C, and the ensuing changes to the +Containerfile. + +Also many thanks to @mairin for the awesome ASCII art! diff --git a/contrib/helloimage/podman_hello_world.bash b/contrib/helloimage/podman_hello_world.bash deleted file mode 100755 index c28141174..000000000 --- a/contrib/helloimage/podman_hello_world.bash +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -### -# ASCII art by the incomparable Máirín Duffy, -# duffy@redhat.com, Twitter: @mairin -# January 2022 -### -echo " " -echo "! ... Hello Podman World ...!" -echo " " -echo " .--\"--. " -echo " / - - \\ " -echo " / (O) (O) \\ " -echo " ~~~| -=(,Y,)=- | " -echo " .---. /\` \\ |~~ " -echo " ~/ o o \\~~~~.----. ~~ " -echo " | =(X)= |~ / (O (O) \\ " -echo " ~~~~~~~ ~| =(Y_)=- | " -echo " ~~~~ ~~~| U |~~ " -echo "" -echo "Project: https://github.com/containers/podman" -echo "Website: https://podman.io" -echo "Documents: https://docs.podman.io" -echo "Twitter: @Podman_io" diff --git a/contrib/helloimage/podman_hello_world.c b/contrib/helloimage/podman_hello_world.c new file mode 100644 index 000000000..ee574130d --- /dev/null +++ b/contrib/helloimage/podman_hello_world.c @@ -0,0 +1,26 @@ +//### +// ASCII art by the incomparable Máirín Duffy, +// duffy@redhat.com, Twitter: @mairin +// January 2022 +//### + +#include <stdio.h> +int main() { + puts("\ +!... Hello Podman World ...!\n\ +\n\ + .--\"--. \n\ + / - - \\ \n\ + / (O) (O) \\ \n\ + ~~~| -=(,Y,)=- | \n\ + .---. /` \\ |~~ \n\ + ~/ o o \\~~~~.----. ~~ \n\ + | =(X)= |~ / (O (O) \\ \n\ + ~~~~~~~ ~| =(Y_)=- | \n\ + ~~~~ ~~~| U |~~ \n\ +\n\ +Project: https://github.com/containers/podman\n\ +Website: https://podman.io\n\ +Documents: https://docs.podman.io\n\ +Twitter: @Podman_io"); +} @@ -12,7 +12,7 @@ require ( github.com/containernetworking/cni v1.0.1 github.com/containernetworking/plugins v1.0.1 github.com/containers/buildah v1.24.1 - github.com/containers/common v0.47.3 + github.com/containers/common v0.47.4 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.19.1 github.com/containers/ocicrypt v1.1.2 @@ -325,8 +325,9 @@ github.com/containernetworking/plugins v1.0.1 h1:wwCfYbTCj5FC0EJgyzyjTXmqysOiJE9 github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containers/buildah v1.24.1 h1:PlvU0hbUsm1x4H9kPcsmqjViqDGnBpSZT3QtZ00RtgI= github.com/containers/buildah v1.24.1/go.mod h1:sE7AaoPQYwAB7dleOOKOpzOO3bA8lRUvZRiZcn/RYi0= -github.com/containers/common v0.47.3 h1:pRT7gkLrBSQe3075j5hoHYeeKpGTWBJHws+tS5xxfak= github.com/containers/common v0.47.3/go.mod h1:/VAV4ibC27Lfyb9cxXM4uTYrJFa/7s+utNB052MJdzY= +github.com/containers/common v0.47.4 h1:kS202Z/bTQIM/pwyuJ+lF8143Uli6AB9Q9OVR0xa9CM= +github.com/containers/common v0.47.4/go.mod h1:HgX0mFXyB0Tbe2REEIp9x9CxET6iSzmHfwR6S/t2LZc= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.19.1 h1:g4/+XIuh1kRoRn2MfLDhfHhkNOIO9JtqhSyo55tjpfE= diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 95f1634a8..afa351c17 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -2045,19 +2045,8 @@ func (c *Container) generateResolvConf() (string, error) { } } - ipv6 := false - // If network status is set check for ipv6 and dns namesevers netStatus := c.getNetworkStatus() for _, status := range netStatus { - for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Subnets { - // Note: only using To16() does not work since it also returns a valid ip for ipv4 - if netAddress.IPNet.IP.To4() == nil && netAddress.IPNet.IP.To16() != nil { - ipv6 = true - } - } - } - if status.DNSServerIPs != nil { for _, nsIP := range status.DNSServerIPs { networkNameServers = append(networkNameServers, nsIP.String()) @@ -2070,16 +2059,9 @@ func (c *Container) generateResolvConf() (string, error) { } } - if c.config.NetMode.IsSlirp4netns() { - ctrNetworkSlipOpts := []string{} - if c.config.NetworkOptions != nil { - ctrNetworkSlipOpts = append(ctrNetworkSlipOpts, c.config.NetworkOptions["slirp4netns"]...) - } - slirpOpts, err := parseSlirp4netnsNetworkOptions(c.runtime, ctrNetworkSlipOpts) - if err != nil { - return "", err - } - ipv6 = slirpOpts.enableIPv6 + ipv6, err := c.checkForIPv6(netStatus) + if err != nil { + return "", err } // Ensure that the container's /etc/resolv.conf is compatible with its @@ -2160,6 +2142,116 @@ func (c *Container) generateResolvConf() (string, error) { return destPath, nil } +// Check if a container uses IPv6. +func (c *Container) checkForIPv6(netStatus map[string]types.StatusBlock) (bool, error) { + for _, status := range netStatus { + for _, netInt := range status.Interfaces { + for _, netAddress := range netInt.Subnets { + // Note: only using To16() does not work since it also returns a valid ip for ipv4 + if netAddress.IPNet.IP.To4() == nil && netAddress.IPNet.IP.To16() != nil { + return true, nil + } + } + } + } + + if c.config.NetMode.IsSlirp4netns() { + ctrNetworkSlipOpts := []string{} + if c.config.NetworkOptions != nil { + ctrNetworkSlipOpts = append(ctrNetworkSlipOpts, c.config.NetworkOptions["slirp4netns"]...) + } + slirpOpts, err := parseSlirp4netnsNetworkOptions(c.runtime, ctrNetworkSlipOpts) + if err != nil { + return false, err + } + return slirpOpts.enableIPv6, nil + } + + return false, nil +} + +// Add a new nameserver to the container's resolv.conf, ensuring that it is the +// first nameserver present. +// Usable only with running containers. +func (c *Container) addNameserver(ips []string) error { + // Take no action if container is not running. + if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { + return nil + } + + // Do we have a resolv.conf at all? + path, ok := c.state.BindMounts["/etc/resolv.conf"] + if !ok { + return nil + } + + // Read in full contents, parse out existing nameservers + contents, err := ioutil.ReadFile(path) + if err != nil { + return err + } + ns := resolvconf.GetNameservers(contents) + options := resolvconf.GetOptions(contents) + search := resolvconf.GetSearchDomains(contents) + + // We could verify that it doesn't already exist + // but extra nameservers shouldn't harm anything. + // Ensure we are the first entry in resolv.conf though, otherwise we + // might be after user-added servers. + ns = append(ips, ns...) + + // We're rewriting the container's resolv.conf as part of this, but we + // hold the container lock, so there should be no risk of parallel + // modification. + if _, err := resolvconf.Build(path, ns, search, options); err != nil { + return errors.Wrapf(err, "error adding new nameserver to container %s resolv.conf", c.ID()) + } + + return nil +} + +// Remove an entry from the existing resolv.conf of the container. +// Usable only with running containers. +func (c *Container) removeNameserver(ips []string) error { + // Take no action if container is not running. + if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { + return nil + } + + // Do we have a resolv.conf at all? + path, ok := c.state.BindMounts["/etc/resolv.conf"] + if !ok { + return nil + } + + // Read in full contents, parse out existing nameservers + contents, err := ioutil.ReadFile(path) + if err != nil { + return err + } + ns := resolvconf.GetNameservers(contents) + options := resolvconf.GetOptions(contents) + search := resolvconf.GetSearchDomains(contents) + + toRemove := make(map[string]bool) + for _, ip := range ips { + toRemove[ip] = true + } + + newNS := make([]string, 0, len(ns)) + for _, server := range ns { + if !toRemove[server] { + newNS = append(newNS, server) + } + } + + if _, err := resolvconf.Build(path, newNS, search, options); err != nil { + return errors.Wrapf(err, "error removing nameservers from container %s resolv.conf", c.ID()) + } + + return nil +} + // updateHosts updates the container's hosts file func (c *Container) updateHosts(path string) error { var hosts string diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index e55e9d114..19d5c7f76 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -1170,6 +1170,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro } // update network status if container is running + oldStatus, statusExist := networkStatus[netName] delete(networkStatus, netName) c.state.NetworkStatus = networkStatus err = c.save() @@ -1180,8 +1181,26 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro // Reload ports when there are still connected networks, maybe we removed the network interface with the child ip. // Reloading without connected networks does not make sense, so we can skip this step. if rootless.IsRootless() && len(networkStatus) > 0 { - return c.reloadRootlessRLKPortMapping() + if err := c.reloadRootlessRLKPortMapping(); err != nil { + return err + } + } + + // Update resolv.conf if required + if statusExist { + stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs)) + for _, ip := range oldStatus.DNSServerIPs { + stringIPs = append(stringIPs, ip.String()) + } + if len(stringIPs) == 0 { + return nil + } + logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs) + if err := c.removeNameserver(stringIPs); err != nil { + return err + } } + return nil } @@ -1263,11 +1282,36 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe if err != nil { return err } + // The first network needs a port reload to set the correct child ip for the rootlessport process. // Adding a second network does not require a port reload because the child ip is still valid. if rootless.IsRootless() && len(networks) == 0 { - return c.reloadRootlessRLKPortMapping() + if err := c.reloadRootlessRLKPortMapping(); err != nil { + return err + } } + + ipv6, err := c.checkForIPv6(networkStatus) + if err != nil { + return err + } + + // Update resolv.conf if required + stringIPs := make([]string, 0, len(results[netName].DNSServerIPs)) + for _, ip := range results[netName].DNSServerIPs { + if (ip.To4() == nil) && !ipv6 { + continue + } + stringIPs = append(stringIPs, ip.String()) + } + if len(stringIPs) == 0 { + return nil + } + logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs) + if err := c.addNameserver(stringIPs); err != nil { + return err + } + return nil } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 3799b463f..44364100e 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -192,6 +192,11 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf } // Reset the log path to point to the default ctr.config.LogPath = "" + // Later in validate() the check is for nil. JSONDeepCopy sets it to an empty + // object. Resetting it to nil if it was nil before. + if config.StaticMAC == nil { + ctr.config.StaticMAC = nil + } } ctr.config.Spec = rSpec diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index 71d46ce70..16f499d4c 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -17,22 +17,37 @@ import ( ) func CreateNetwork(w http.ResponseWriter, r *http.Request) { + if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { + utils.BadRequest(w, "version", v.String(), err) + return + } + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) network := types.Network{} if err := json.NewDecoder(r.Body).Decode(&network); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "decode body")) + utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to decode request JSON payload")) return } ic := abi.ContainerEngine{Libpod: runtime} report, err := ic.NetworkCreate(r.Context(), network) if err != nil { - utils.InternalServerError(w, err) + if errors.Is(err, types.ErrNetworkExists) { + utils.Error(w, http.StatusConflict, err) + } else { + utils.InternalServerError(w, err) + } return } utils.WriteResponse(w, http.StatusOK, report) } + func ListNetworks(w http.ResponseWriter, r *http.Request) { + if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { + utils.BadRequest(w, "version", v.String(), err) + return + } + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) filterMap, err := util.PrepareFilters(r) if err != nil { @@ -54,6 +69,11 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { } func RemoveNetwork(w http.ResponseWriter, r *http.Request) { + if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { + utils.BadRequest(w, "version", v.String(), err) + return + } + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { @@ -87,21 +107,18 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, reports) } +// InspectNetwork reports on given network's details func InspectNetwork(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) - query := struct { - }{ - // override any golang type defaults - } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { + utils.BadRequest(w, "version", v.String(), err) return } + + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + ic := abi.ContainerEngine{Libpod: runtime} + name := utils.GetName(r) options := entities.InspectOptions{} - ic := abi.ContainerEngine{Libpod: runtime} reports, errs, err := ic.NetworkInspect(r.Context(), []string{name}, options) // If the network cannot be found, we return a 404. if len(errs) > 0 { @@ -117,14 +134,19 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { // Connect adds a container to a network func Connect(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { + utils.BadRequest(w, "version", v.String(), err) + return + } + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) var netConnect entities.NetworkConnectOptions if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to decode request JSON payload")) return } name := utils.GetName(r) + err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netConnect.PerNetworkOptions) if err != nil { if errors.Cause(err) == define.ErrNoSuchCtr { @@ -143,10 +165,15 @@ func Connect(w http.ResponseWriter, r *http.Request) { // ExistsNetwork check if a network exists func ExistsNetwork(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - name := utils.GetName(r) + if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { + utils.BadRequest(w, "version", v.String(), err) + return + } + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) ic := abi.ContainerEngine{Libpod: runtime} + + name := utils.GetName(r) report, err := ic.NetworkExists(r.Context(), name) if err != nil { utils.Error(w, http.StatusInternalServerError, err) @@ -161,7 +188,13 @@ func ExistsNetwork(w http.ResponseWriter, r *http.Request) { // Prune removes unused networks func Prune(w http.ResponseWriter, r *http.Request) { + if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { + utils.BadRequest(w, "version", v.String(), err) + return + } + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + ic := abi.ContainerEngine{Libpod: runtime} filterMap, err := util.PrepareFilters(r) if err != nil { @@ -172,7 +205,6 @@ func Prune(w http.ResponseWriter, r *http.Request) { pruneOptions := entities.NetworkPruneOptions{ Filters: *filterMap, } - ic := abi.ContainerEngine{Libpod: runtime} pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions) if err != nil { utils.Error(w, http.StatusInternalServerError, err) diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index baa1fe6fb..4466c938f 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -320,6 +320,8 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // $ref: "#/responses/NetworkCreateReport" // 400: // $ref: "#/responses/BadParamError" + // 409: + // $ref: "#/responses/ConflictError" // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/networks/create"), s.APIHandler(libpod.CreateNetwork)).Methods(http.MethodPost) diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index a363f2c6e..c508cb767 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -352,11 +352,13 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } c = tmpFile.Name() } + c = filepath.Clean(c) cfDir := filepath.Dir(c) if absDir, err := filepath.EvalSymlinks(cfDir); err == nil { name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator))) c = filepath.Join(absDir, name) } + containerfile, err := filepath.Abs(c) if err != nil { logrus.Errorf("Cannot find absolute path of %v: %v", c, err) diff --git a/pkg/bindings/test/networks_test.go b/pkg/bindings/test/networks_test.go index ee2d6f472..137db71a3 100644 --- a/pkg/bindings/test/networks_test.go +++ b/pkg/bindings/test/networks_test.go @@ -80,7 +80,7 @@ var _ = Describe("Podman networks", func() { // Valid filter params => network should be pruned now. filters = map[string][]string{ - "until": {"5000000000"}, //June 11, 2128 + "until": {"5000000000"}, // June 11, 2128 } pruneResponse, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filters)) Expect(err).To(BeNil()) @@ -105,7 +105,7 @@ var _ = Describe("Podman networks", func() { _, err = network.Create(connText, &net) Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) - Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + Expect(code).To(BeNumerically("==", http.StatusConflict)) }) It("inspect network", func() { diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go index 1ebd6a455..270b5b6c4 100644 --- a/pkg/checkpoint/checkpoint_restore.go +++ b/pkg/checkpoint/checkpoint_restore.go @@ -140,6 +140,13 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod) } ctrConfig.NetNsCtr = infraContainer.ID() + for net, opts := range ctrConfig.Networks { + opts.StaticIPs = nil + opts.StaticMAC = nil + ctrConfig.Networks[net] = opts + } + ctrConfig.StaticIP = nil + ctrConfig.StaticMAC = nil } if ctrConfig.PIDNsCtr != "" { diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 60ab471ee..4d3e2edf4 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -14,6 +14,7 @@ import ( "strings" "github.com/coreos/stream-metadata-go/fedoracoreos" + "github.com/coreos/stream-metadata-go/release" "github.com/coreos/stream-metadata-go/stream" "github.com/pkg/errors" @@ -28,6 +29,14 @@ var ( Format string = "qcow2.xz" ) +const ( + // Used for testing the latest podman in fcos + // special builds + podmanTesting = "podman-testing" + PodmanTestingHost = "fedorapeople.org" + PodmanTestingURL = "groups/podman/testing" +) + type FcosDownload struct { Download } @@ -111,14 +120,39 @@ func getFcosArch() string { return arch } +// getStreamURL is a wrapper for the fcos.GetStream URL +// so that we can inject a special stream and url for +// testing podman before it merges into fcos builds +func getStreamURL(streamType string) url2.URL { + // For the podmanTesting stream type, we point to + // a custom url on fedorapeople.org + if streamType == podmanTesting { + return url2.URL{ + Scheme: "https", + Host: PodmanTestingHost, + Path: fmt.Sprintf("%s/%s.json", PodmanTestingURL, "podman4"), + } + } + return fedoracoreos.GetStreamURL(streamType) +} + // This should get Exported and stay put as it will apply to all fcos downloads // getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { var ( fcosstable stream.Stream + altMeta release.Release streamType string ) + + // This is being hard set to testing. Once podman4 is in the + // fcos trees, we should remove it and re-release at least on + // macs. + imageStream = "podman-testing" + switch imageStream { + case "podman-testing": + streamType = "podman-testing" case "testing", "": streamType = fedoracoreos.StreamTesting case "next": @@ -128,7 +162,7 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { default: return nil, errors.Errorf("invalid stream %s: valid streams are `testing` and `stable`", imageStream) } - streamurl := fedoracoreos.GetStreamURL(streamType) + streamurl := getStreamURL(streamType) resp, err := http.Get(streamurl.String()) if err != nil { return nil, err @@ -142,6 +176,27 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { logrus.Error(err) } }() + if imageStream == podmanTesting { + if err := json.Unmarshal(body, &altMeta); err != nil { + return nil, err + } + + arches, ok := altMeta.Architectures[getFcosArch()] + if !ok { + return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream") + } + qcow2, ok := arches.Media.Qemu.Artifacts["qcow2.xz"] + if !ok { + return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream") + } + disk := qcow2.Disk + + return &fcosDownloadInfo{ + Location: disk.Location, + Sha256Sum: disk.Sha256, + CompressionType: "xz", + }, nil + } if err := json.Unmarshal(body, &fcosstable); err != nil { return nil, err diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index 0e2389bd5..3502b89e0 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -8,7 +8,10 @@ t GET networks/non-existing-network 404 \ t POST libpod/networks/create name='"network1"' 200 \ .name=network1 \ - .created~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \ + .created~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* + +t POST /v3.4.0/libpod/networks/create name='"bad_version"' 400 \ + .cause='given version is not supported' # --data '{"name":"network2","subnets":[{"subnet":"10.10.254.0/24"}],"Labels":{"abc":"val"}}' t POST libpod/networks/create name='"network2"' \ diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index 56280f04e..bd728e130 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -256,11 +256,11 @@ function t() { # If given path begins with /, use it as-is; otherwise prepend /version/ local url=http://$HOST:$PORT - if expr "$path" : "/" >/dev/null; then - url="$url$path" - else - url="$url/v1.40/$path" - fi + case "$path" in + /*) url="$url$path" ;; + libpod/*) url="$url/v4.0.0/$path" ;; + *) url="$url/v1.41/$path" ;; + esac # Log every action we do echo "-------------------------------------------------------------" >>$LOG diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 5f1e4b1d1..5abc672e9 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -1081,10 +1081,6 @@ var _ = Describe("Podman checkpoint", func() { }) namespaceCombination := []string{ - "cgroup,ipc,net,uts,pid", - "cgroup,ipc,net,uts", - "cgroup,ipc,net", - "cgroup,ipc", "ipc,net,uts,pid", "ipc,net,uts", "ipc,net", diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index f843a8984..b1cd76d27 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -771,15 +771,15 @@ func SkipIfNotActive(unit string, reason string) { } } -func SkipIfNetavark(p *PodmanTestIntegration) { - if p.NetworkBackend == Netavark { - Skip("This test is not compatible with the netavark network backend") +func SkipIfCNI(p *PodmanTestIntegration) { + if p.NetworkBackend == CNI { + Skip("this test is not compatible with the CNI network backend") } } -func SkipUntilAardvark(p *PodmanTestIntegration) { +func SkipIfNetavark(p *PodmanTestIntegration) { if p.NetworkBackend == Netavark { - Skip("Re-enable when aardvark is functional") + Skip("This test is not compatible with the netavark network backend") } } @@ -1038,3 +1038,7 @@ func ncz(port int) bool { } return false } + +func createNetworkName(name string) string { + return name + stringid.GenerateNonCryptoID()[:10] +} diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index baef142ba..a0716c84d 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -2,6 +2,7 @@ package integration import ( "os" + "strings" . "github.com/containers/podman/v4/test/utils" "github.com/containers/storage/pkg/stringid" @@ -77,6 +78,11 @@ var _ = Describe("Podman network connect and disconnect", func() { Expect(session).Should(Exit(0)) defer podmanTest.removeNetwork(netName) + gw := podmanTest.Podman([]string{"network", "inspect", netName, "--format", "{{(index .Subnets 0).Gateway}}"}) + gw.WaitWithDefaultTimeout() + Expect(gw).Should(Exit(0)) + ns := gw.OutputToString() + ctr := podmanTest.Podman([]string{"run", "-dt", "--name", "test", "--network", netName, ALPINE, "top"}) ctr.WaitWithDefaultTimeout() Expect(ctr).Should(Exit(0)) @@ -85,6 +91,11 @@ var _ = Describe("Podman network connect and disconnect", func() { exec.WaitWithDefaultTimeout() Expect(exec).Should(Exit(0)) + exec2 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) + exec2.WaitWithDefaultTimeout() + Expect(exec2).Should(Exit(0)) + Expect(strings.Contains(exec2.OutputToString(), ns)).To(BeTrue()) + dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"}) dis.WaitWithDefaultTimeout() Expect(dis).Should(Exit(0)) @@ -98,6 +109,11 @@ var _ = Describe("Podman network connect and disconnect", func() { exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"}) exec.WaitWithDefaultTimeout() Expect(exec).Should(ExitWithError()) + + exec3 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) + exec3.WaitWithDefaultTimeout() + Expect(exec3).Should(Exit(0)) + Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeFalse()) }) It("bad network name in connect should result in error", func() { @@ -182,6 +198,16 @@ var _ = Describe("Podman network connect and disconnect", func() { Expect(session).Should(Exit(0)) defer podmanTest.removeNetwork(newNetName) + gw := podmanTest.Podman([]string{"network", "inspect", newNetName, "--format", "{{(index .Subnets 0).Gateway}}"}) + gw.WaitWithDefaultTimeout() + Expect(gw).Should(Exit(0)) + ns := gw.OutputToString() + + exec2 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) + exec2.WaitWithDefaultTimeout() + Expect(exec2).Should(Exit(0)) + Expect(strings.Contains(exec2.OutputToString(), ns)).To(BeFalse()) + ip := "10.11.100.99" mac := "44:11:44:11:44:11" connect := podmanTest.Podman([]string{"network", "connect", "--ip", ip, "--mac-address", mac, newNetName, "test"}) @@ -206,6 +232,11 @@ var _ = Describe("Podman network connect and disconnect", func() { Expect(exec.OutputToString()).Should(ContainSubstring(ip)) Expect(exec.OutputToString()).Should(ContainSubstring(mac)) + exec3 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"}) + exec3.WaitWithDefaultTimeout() + Expect(exec3).Should(Exit(0)) + Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeTrue()) + // make sure no logrus errors are shown https://github.com/containers/podman/issues/9602 rm := podmanTest.Podman([]string{"rm", "--time=0", "-f", "test"}) rm.WaitWithDefaultTimeout() diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index 7589adaab..395759ee6 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -330,8 +330,8 @@ var _ = Describe("Podman network create", func() { Expect(nc).To(ExitWithError()) }) - It("podman network create with internal should not have dnsname", func() { - SkipUntilAardvark(podmanTest) + It("podman CNI network create with internal should not have dnsname", func() { + SkipIfNetavark(podmanTest) net := "internal-test" + stringid.GenerateNonCryptoID() nc := podmanTest.Podman([]string{"network", "create", "--internal", net}) nc.WaitWithDefaultTimeout() @@ -348,6 +348,24 @@ var _ = Describe("Podman network create", func() { Expect(nc.OutputToString()).ToNot(ContainSubstring("dnsname")) }) + It("podman Netavark network create with internal should have dnsname", func() { + SkipIfCNI(podmanTest) + net := "internal-test" + stringid.GenerateNonCryptoID() + nc := podmanTest.Podman([]string{"network", "create", "--internal", net}) + nc.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + Expect(nc).Should(Exit(0)) + // Not performing this check on remote tests because it is a logrus error which does + // not come back via stderr on the remote client. + if !IsRemote() { + Expect(nc.ErrorToString()).To(BeEmpty()) + } + nc = podmanTest.Podman([]string{"network", "inspect", net}) + nc.WaitWithDefaultTimeout() + Expect(nc).Should(Exit(0)) + Expect(nc.OutputToString()).To(ContainSubstring(`"dns_enabled": true`)) + }) + It("podman network create with invalid name", func() { for _, name := range []string{"none", "host", "bridge", "private", "slirp4netns", "container", "ns"} { nc := podmanTest.Podman([]string{"network", "create", name}) diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index bd30a1f5d..89a9005f5 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -466,10 +466,61 @@ var _ = Describe("Podman network", func() { Expect(lines[1]).To(Equal(netName2)) }) - It("podman network with multiple aliases", func() { - SkipUntilAardvark(podmanTest) + It("podman CNI network with multiple aliases", func() { + SkipIfNetavark(podmanTest) + var worked bool + netName := createNetworkName("aliasTest") + session := podmanTest.Podman([]string{"network", "create", netName}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(netName) + Expect(session).Should(Exit(0)) + + interval := time.Duration(250 * time.Millisecond) + for i := 0; i < 6; i++ { + n := podmanTest.Podman([]string{"network", "exists", netName}) + n.WaitWithDefaultTimeout() + worked = n.ExitCode() == 0 + if worked { + break + } + time.Sleep(interval) + interval *= 2 + } + + top := podmanTest.Podman([]string{"run", "-dt", "--name=web", "--network=" + netName, "--network-alias=web1", "--network-alias=web2", nginx}) + top.WaitWithDefaultTimeout() + Expect(top).Should(Exit(0)) + interval = time.Duration(250 * time.Millisecond) + // Wait for the nginx service to be running + for i := 0; i < 6; i++ { + // Test curl against the container's name + c1 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web"}) + c1.WaitWithDefaultTimeout() + worked = c1.ExitCode() == 0 + if worked { + break + } + time.Sleep(interval) + interval *= 2 + } + Expect(worked).To(BeTrue()) + + // Nginx is now running so no need to do a loop + // Test against the first alias + c2 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web1"}) + c2.WaitWithDefaultTimeout() + Expect(c2).Should(Exit(0)) + + // Test against the second alias + c3 := podmanTest.Podman([]string{"run", "--dns-search", "dns.podman", "--network=" + netName, nginx, "curl", "web2"}) + c3.WaitWithDefaultTimeout() + Expect(c3).Should(Exit(0)) + }) + + It("podman Netavark network with multiple aliases", func() { + SkipIfCNI(podmanTest) var worked bool - netName := "aliasTest" + stringid.GenerateNonCryptoID() + netName := createNetworkName("aliasTest") session := podmanTest.Podman([]string{"network", "create", netName}) session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(netName) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 4c056df10..aa1887f84 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -715,8 +715,8 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(run.OutputToString()).To(ContainSubstring(ipAddr)) }) - It("podman cni network works across user ns", func() { - SkipUntilAardvark(podmanTest) + It("podman CNI network works across user ns", func() { + SkipIfNetavark(podmanTest) netName := stringid.GenerateNonCryptoID() create := podmanTest.Podman([]string{"network", "create", netName}) create.WaitWithDefaultTimeout() @@ -740,6 +740,31 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(log.OutputToString()).To(Equal("podman")) }) + It("podman Netavark network works across user ns", func() { + SkipIfCNI(podmanTest) + netName := createNetworkName("") + create := podmanTest.Podman([]string{"network", "create", netName}) + create.WaitWithDefaultTimeout() + Expect(create).Should(Exit(0)) + defer podmanTest.removeNetwork(netName) + + name := "nc-server" + run := podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "-d", "--name", name, "--net", netName, ALPINE, "nc", "-l", "-p", "9480"}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + + // NOTE: we force the k8s-file log driver to make sure the + // tests are passing inside a container. + run = podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "--rm", "--net", netName, "--uidmap", "0:1:4096", ALPINE, "sh", "-c", fmt.Sprintf("echo podman | nc -w 1 %s.dns.podman 9480", name)}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + + log := podmanTest.Podman([]string{"logs", name}) + log.WaitWithDefaultTimeout() + Expect(log).Should(Exit(0)) + Expect(log.OutputToString()).To(Equal("podman")) + }) + It("podman run with new:pod and static-ip", func() { netName := stringid.GenerateNonCryptoID() ipAddr := "10.25.40.128" @@ -814,14 +839,50 @@ EXPOSE 2004-2005/tcp`, ALPINE) pingTest("--net=private") }) - It("podman run check dnsname plugin", func() { - SkipUntilAardvark(podmanTest) + It("podman run check dnsname plugin with CNI", func() { + SkipIfNetavark(podmanTest) + pod := "testpod" + session := podmanTest.Podman([]string{"pod", "create", "--name", pod}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + net := createNetworkName("IntTest") + session = podmanTest.Podman([]string{"network", "create", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + Expect(session).Should(Exit(0)) + + pod2 := "testpod2" + session = podmanTest.Podman([]string{"pod", "create", "--network", net, "--name", pod2}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--name", "con1", "--network", net, ALPINE, "nslookup", "con1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--name", "con2", "--pod", pod, "--network", net, ALPINE, "nslookup", "con2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--name", "con3", "--pod", pod2, ALPINE, "nslookup", "con1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(1)) + Expect(session.ErrorToString()).To(ContainSubstring("can't resolve 'con1'")) + + session = podmanTest.Podman([]string{"run", "--name", "con4", "--network", net, ALPINE, "nslookup", pod2 + ".dns.podman"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + }) + + It("podman run check dnsname plugin with Netavark", func() { + SkipIfCNI(podmanTest) pod := "testpod" session := podmanTest.Podman([]string{"pod", "create", "--name", pod}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - net := "IntTest" + stringid.GenerateNonCryptoID() + net := createNetworkName("IntTest") session = podmanTest.Podman([]string{"network", "create", net}) session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(net) @@ -850,9 +911,23 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(session).Should(Exit(0)) }) - It("podman run check dnsname adds dns search domain", func() { - SkipUntilAardvark(podmanTest) - net := "dnsname" + stringid.GenerateNonCryptoID() + It("podman run check dnsname adds dns search domain with CNI", func() { + SkipIfNetavark(podmanTest) + net := createNetworkName("dnsname") + session := podmanTest.Podman([]string{"network", "create", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--network", net, ALPINE, "cat", "/etc/resolv.conf"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("search dns.podman")) + }) + + It("podman run check dnsname adds dns search domain with Netavark", func() { + SkipIfCNI(podmanTest) + net := createNetworkName("dnsname") session := podmanTest.Podman([]string{"network", "create", net}) session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(net) diff --git a/test/system/070-build.bats b/test/system/070-build.bats index d5f7365e8..a95acd986 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -88,12 +88,10 @@ EOF containerfile=$PODMAN_TMPDIR/Containerfile cat >$containerfile <<EOF FROM $IMAGE -RUN apk add nginx RUN echo $rand_content > /$rand_filename EOF - # The 'apk' command can take a long time to fetch files; bump timeout - PODMAN_TIMEOUT=240 run_podman build -t build_test -f - --format=docker $tmpdir < $containerfile + run_podman build -t build_test -f - --format=docker $tmpdir < $containerfile is "$output" ".*COMMIT" "COMMIT seen in log" run_podman run --rm build_test cat /$rand_filename @@ -188,6 +186,30 @@ EOF run_podman rmi -f build_test $iid } +@test "podman build test -f ./relative" { + rand_filename=$(random_string 20) + rand_content=$(random_string 50) + + tmpdir=$PODMAN_TMPDIR/build-test + mkdir -p $tmpdir + mkdir -p $PODMAN_TMPDIR/reldir + + containerfile=$PODMAN_TMPDIR/reldir/Containerfile + cat >$containerfile <<EOF +FROM $IMAGE +RUN echo $rand_content > /$rand_filename +EOF + + cd $PODMAN_TMPDIR + run_podman build -t build_test -f ./reldir/Containerfile --format=docker $tmpdir + is "$output" ".*COMMIT" "COMMIT seen in log" + + run_podman run --rm build_test cat /$rand_filename + is "$output" "$rand_content" "reading generated file in image" + + run_podman rmi -f build_test +} + @test "podman build - URLs" { tmpdir=$PODMAN_TMPDIR/build-test mkdir -p $tmpdir diff --git a/troubleshooting.md b/troubleshooting.md index 82ca64305..6f2a96a56 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -916,6 +916,235 @@ After deleting a VM on macOS, the initialization of subsequent VMs fails. After deleting a client VM on macOS via `podman machine stop` && `podman machine rm`, attempting to `podman machine init` a new client VM leads to an error with the 127.0.0.1:7777 port already bound. -### Solution +#### Solution You will need to remove the hanging gv-proxy process bound to the port in question. For example, if the port mentioned in the error message is 127.0.0.1:7777, you can use the command `kill -9 $(lsof -i:7777)` in order to identify and remove the hanging process which prevents you from starting a new VM on that default port. + +### 32) The sshd process fails to run inside of the container. + +#### Symptom + +The sshd process running inside the container fails with the error +"Error writing /proc/self/loginuid". + +### Solution + +If the `/proc/self/loginuid` file is already initialized then the +`CAP_AUDIT_CONTROL` capability is required to override it. + +This happens when running Podman from a user session since the +`/proc/self/loginuid` file is already initialized. The solution is to +run Podman from a system service, either using the Podman service, and +then using podman -remote to start the container or simply by running +something like `systemd-run podman run ...`. In this case the +container will only need `CAP_AUDIT_WRITE`. + +### 33) Container creates a file that is not owned by the user's regular UID + +After running a container with rootless Podman, the non-root user sees a numerical UID and GID instead of a username and groupname. + +#### Symptom + +When listing file permissions with `ls -l` on the host in a directory that was passed as `--volume /some/dir` to `podman run`, +the UID and GID are displayed rather than the corresponding username and groupname. The UID and GID numbers displayed are +from the user's subordinate UID and GID ranges on the host system. + +An example + +```Text +$ mkdir dir1 +$ chmod 777 dir1 +$ podman run --rm -v ./dir1:/dir1:Z \ + --user 2003:2003 \ + docker.io/library/ubuntu bash -c "touch /dir1/a; chmod 600 /dir1/a" +$ ls -l dir1/a +-rw-------. 1 102002 102002 0 Jan 19 19:35 dir1/a +$ less dir1/a +less: dir1/a: Permission denied +``` + +#### Solution + +If you want to read or remove such a file, you can do so by entering a user namespace. +Instead of running commands such as `less dir1/a` or `rm dir1/a`, you would need to +prepend the command-line with `podman unshare`, i.e., +`podman unshare less dir1/a` or `podman unshare rm dir1/a`. To be able to use Bash +features, such as variable expansion and globbing, you need to wrap the command with +`bash -c`, e.g. `podman unshare bash -c 'ls $HOME/dir1/a*'`. + +Would it have been possible to run Podman in another way so that your regular +user would have become the owner of the file? Yes, you can use the options +__--uidmap__ and __--gidmap__ to change how UIDs and GIDs are mapped +between the container and the host. Let's try it out. + +In the example above `ls -l` shows the UID 102002 and GID 102002. Set shell variables + +```Text +$ uid_from_ls = 102002 +$ gid_from_ls = 102002 +``` + +Set shell variables to the lowest subordinate UID and GID + +```Text +$ lowest_subuid=$(podman info --format "{{ (index .Host.IDMappings.UIDMap 1).HostID }}") +$ lowest_subgid=$(podman info --format "{{ (index .Host.IDMappings.GIDMap 1).HostID }}") +``` + +Compute the UID and GID inside the container that map to the owner of the created file on the host. + +```Text +$ uid=$(( $uid_from_ls - $lowest_subuid + 1)) +$ gid=$(( $gid_from_ls - $lowest_subgid + 1)) +``` +(In the computation it was assumed that there is only one subuid range and one subgid range) + +```Text +$ echo $uid +2003 +$ echo $gid +2003 +``` + +The computation shows that the UID is _2003_ and the GID is _2003_ inside the container. +This comes as no surprise as this is what was specified before with `--user=2003:2003`, +but the same computation could be used whenever a username is specified +or the __--user__ option is not used. + +Run the container again but now with UIDs and GIDs mapped + +```Text +$ subuidSize=$(( $(podman info --format "{{ range .Host.IDMappings.UIDMap }}+{{.Size }}{{end }}" ) - 1 )) +$ subgidSize=$(( $(podman info --format "{{ range .Host.IDMappings.GIDMap }}+{{.Size }}{{end }}" ) - 1 )) +$ mkdir dir1 +$ chmod 777 dir1 +$ podman run --rm + -v ./dir1:/dir1:Z \ + --user $uid:$gid \ + --uidmap $uid:0:1 \ + --uidmap 0:1:$uid \ + --uidmap $(($uid+1)):$(($uid+1)):$(($subuidSize-$uid)) \ + --gidmap $gid:0:1 \ + --gidmap 0:1:$gid \ + --gidmap $(($gid+1)):$(($gid+1)):$(($subgidSize-$gid)) \ + docker.io/library/ubuntu bash -c "touch /dir1/a; chmod 600 /dir1/a" +$ id -u +tester +$ id -g +tester +$ ls -l dir1/a +-rw-------. 1 tester tester 0 Jan 19 20:31 dir1/a +$ +``` + +In this example the __--user__ option specified a rootless user in the container. +As the rootless user could also have been specified in the container image, e.g., + +```Text +$ podman image inspect --format "user: {{.User}}" IMAGE +user: hpc +$ +``` +the same problem could also occur even without specifying __--user__. + +Another variant of the same problem could occur when using +__--user=root:root__ (the default), but where the root user creates non-root owned files +in some way (e.g by creating them themselves, or switching the effective UID to +a rootless user and then creates files). + +### 34) Passed-in devices or files can't be accessed in rootless container (UID/GID mapping problem) + +As a non-root user you have access rights to devices, files and directories that you +want to pass into a rootless container with `--device=...`, `--volume=...` or `--mount=..`. + +Podman by default maps a non-root user inside a container to one of the user's +subordinate UIDs and subordinate GIDs on the host. When the container user tries to access a +file, a "Permission denied" error could occur because the container user does not have the +permissions of the regular user of the host. + +#### Symptom + +* Any access inside the container is rejected with "Permission denied" +for files, directories or devices passed in to the container +with `--device=..`,`--volume=..` or `--mount=..`, e.g. + +```Text +$ mkdir dir1 +$ chmod 700 dir1 +$ podman run --rm -v ./dir1:/dir1:Z \ + --user 2003:2003 \ + docker.io/library/ubuntu ls /dir1 +ls: cannot open directory '/dir1': Permission denied +``` + +#### Solution + +We follow essentialy the same solution as in the previous +troubleshooting tip: + "_Container creates a file that is not owned by the regular UID_" +but for this problem the container UID and GID can't be as +easily computed by mere addition and subtraction. + +In other words, it might be more challenging to find out the UID and +the GID inside the container that we want to map to the regular +user on the host. + +If the __--user__ option is used together with a numerical UID and GID +to specify a rootless user, we already know the answer. + +If the __--user__ option is used together with a username and groupname, +we could look up the UID and GID in the file _/etc/passwd_ of the container. + +If the container user is not set via __--user__ but instead from the +container image, we could inspect the container image + +```Text +$ podman image inspect --format "user: {{.User}}" IMAGE +user: hpc +$ +``` + +and then look it up in _/etc/passwd_ of the container. + +If the problem occurs in a container that is started to run as root but later +switches to an effictive UID of a rootless user, it might be less +straightforward to find out the UID and the GID. Reading the +_Containerfile_, _Dockerfile_ or the _/etc/passwd_ could give a clue. + +To run the container with the rootless container UID and GID mapped to the +user's regular UID and GID on the host follow these steps: + +Set the _uid_ and _gid_ shell variables in a Bash shell to the UID and GID +of the user that will be running inside the container, e.g. + +```Text +$ uid=2003 +$ gid=2003 +``` + +and run + +```Text +$ mkdir dir1 +$ echo hello > dir1/file.txt +$ chmod 700 dir1/file.txt +$ subuidSize=$(( $(podman info --format "{{ range .Host.IDMappings.UIDMap }}+{{.Size }}{{end }}" ) - 1 )) +$ subgidSize=$(( $(podman info --format "{{ range .Host.IDMappings.GIDMap }}+{{.Size }}{{end }}" ) - 1 )) +$ podman run --rm \ + -v ./dir1:/dir1:Z \ + --user $uid:$gid \ + --uidmap $uid:0:1 \ + --uidmap 0:1:$uid \ + --uidmap $(($uid+1)):$(($uid+1)):$(($subuidSize-$uid)) \ + --gidmap $gid:0:1 \ + --gidmap 0:1:$gid \ + --gidmap $(($gid+1)):$(($gid+1)):$(($subgidSize-$gid)) \ + docker.io/library/alpine cat /dir1/file.txt +hello +$ +``` + +A side-note: Using [__--userns=keep-id__](https://docs.podman.io/en/latest/markdown/podman-run.1.html#userns-mode) +can sometimes be an alternative solution, but it forces the regular +user's host UID to be mapped to the same UID inside the container +so it provides less flexibility than using __--uidmap__ and __--gidmap__. diff --git a/vendor/github.com/containers/common/libimage/copier.go b/vendor/github.com/containers/common/libimage/copier.go index 459989579..2a8f47f7f 100644 --- a/vendor/github.com/containers/common/libimage/copier.go +++ b/vendor/github.com/containers/common/libimage/copier.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/containers/common/libimage/manifests" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/retry" "github.com/containers/image/v5/copy" @@ -26,8 +27,10 @@ const ( ) // LookupReferenceFunc return an image reference based on the specified one. -// This can be used to pass custom blob caches to the copy operation. -type LookupReferenceFunc func(ref types.ImageReference) (types.ImageReference, error) +// The returned reference can return custom ImageSource or ImageDestination +// objects which intercept or filter blobs, manifests, and signatures as +// they are read and written. +type LookupReferenceFunc = manifests.LookupReferenceFunc // CopyOptions allow for customizing image-copy operations. type CopyOptions struct { diff --git a/vendor/github.com/containers/common/libimage/manifests/manifests.go b/vendor/github.com/containers/common/libimage/manifests/manifests.go index 45223cc2f..ccff908c9 100644 --- a/vendor/github.com/containers/common/libimage/manifests/manifests.go +++ b/vendor/github.com/containers/common/libimage/manifests/manifests.go @@ -27,6 +27,12 @@ import ( const instancesData = "instances.json" +// LookupReferenceFunc return an image reference based on the specified one. +// The returned reference can return custom ImageSource or ImageDestination +// objects which intercept or filter blobs, manifests, and signatures as +// they are read and written. +type LookupReferenceFunc func(ref types.ImageReference) (types.ImageReference, error) + // ErrListImageUnknown is returned when we attempt to create an image reference // for a List that has not yet been saved to an image. var ErrListImageUnknown = stderrors.New("unable to determine which image holds the manifest list") @@ -57,6 +63,7 @@ type PushOptions struct { SignBy string // fingerprint of GPG key to use to sign images RemoveSignatures bool // true to discard signatures in images ManifestType string // the format to use when saving the list - possible options are oci, v2s1, and v2s2 + SourceFilter LookupReferenceFunc // filter the list source } // Create creates a new list containing information about the specified image, @@ -221,6 +228,11 @@ func (l *list) Push(ctx context.Context, dest types.ImageReference, options Push if err != nil { return nil, "", err } + if options.SourceFilter != nil { + if src, err = options.SourceFilter(src); err != nil { + return nil, "", err + } + } copyOptions := &cp.Options{ ImageListSelection: options.ImageListSelection, Instances: options.Instances, diff --git a/vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go b/vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go index dedb40ad3..5574b2b1c 100644 --- a/vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go +++ b/vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go @@ -222,14 +222,33 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ err error ) if len(network.Subnets) > 0 { + defIpv4Route := false + defIpv6Route := false for _, subnet := range network.Subnets { - route, err := newIPAMDefaultRoute(util.IsIPv6(subnet.Subnet.IP)) - if err != nil { - return nil, "", err - } - routes = append(routes, route) ipam := newIPAMLocalHostRange(subnet.Subnet, subnet.LeaseRange, subnet.Gateway) ipamRanges = append(ipamRanges, []ipamLocalHostRangeConf{*ipam}) + + // only add default route for not internal networks + if !network.Internal { + ipv6 := util.IsIPv6(subnet.Subnet.IP) + if !ipv6 && defIpv4Route { + continue + } + if ipv6 && defIpv6Route { + continue + } + + if ipv6 { + defIpv6Route = true + } else { + defIpv4Route = true + } + route, err := newIPAMDefaultRoute(ipv6) + if err != nil { + return nil, "", err + } + routes = append(routes, route) + } } ipamConf = newIPAMHostLocalConf(routes, ipamRanges) } else { diff --git a/vendor/github.com/containers/common/libnetwork/cni/config.go b/vendor/github.com/containers/common/libnetwork/cni/config.go index b0aa19d94..b1f89400c 100644 --- a/vendor/github.com/containers/common/libnetwork/cni/config.go +++ b/vendor/github.com/containers/common/libnetwork/cni/config.go @@ -82,7 +82,7 @@ func (n *cniNetwork) networkCreate(newNetwork *types.Network, defaultNet bool) ( return nil, errors.Wrapf(types.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver) } - err = internalutil.ValidateSubnets(newNetwork, usedNetworks) + err = internalutil.ValidateSubnets(newNetwork, !newNetwork.Internal, usedNetworks) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/validate.go b/vendor/github.com/containers/common/libnetwork/internal/util/validate.go index bfc5e2247..ac3934f8d 100644 --- a/vendor/github.com/containers/common/libnetwork/internal/util/validate.go +++ b/vendor/github.com/containers/common/libnetwork/internal/util/validate.go @@ -65,11 +65,11 @@ func ValidateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet) } // ValidateSubnets will validate the subnets for this network. -// It also sets the gateway if the gateway is empty and it sets +// It also sets the gateway if the gateway is empty and addGateway is set to true // IPv6Enabled to true if at least one subnet is ipv6. -func ValidateSubnets(network *types.Network, usedNetworks []*net.IPNet) error { +func ValidateSubnets(network *types.Network, addGateway bool, usedNetworks []*net.IPNet) error { for i := range network.Subnets { - err := ValidateSubnet(&network.Subnets[i], !network.Internal, usedNetworks) + err := ValidateSubnet(&network.Subnets[i], addGateway, usedNetworks) if err != nil { return err } diff --git a/vendor/github.com/containers/common/libnetwork/netavark/config.go b/vendor/github.com/containers/common/libnetwork/netavark/config.go index 7de59f807..16b4e5c53 100644 --- a/vendor/github.com/containers/common/libnetwork/netavark/config.go +++ b/vendor/github.com/containers/common/libnetwork/netavark/config.go @@ -115,16 +115,13 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo return nil, errors.Wrapf(types.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver) } - err = internalutil.ValidateSubnets(newNetwork, usedNetworks) + // add gatway when not internal or dns enabled + addGateway := !newNetwork.Internal || newNetwork.DNSEnabled + err = internalutil.ValidateSubnets(newNetwork, addGateway, usedNetworks) if err != nil { return nil, err } - // FIXME: If we have a working solution for internal networks with dns this check should be removed. - if newNetwork.DNSEnabled && newNetwork.Internal { - return nil, errors.New("cannot set internal and dns enabled") - } - newNetwork.Created = time.Now() if !defaultNet { diff --git a/vendor/github.com/containers/common/libnetwork/netavark/network.go b/vendor/github.com/containers/common/libnetwork/netavark/network.go index 7122acf98..efea36fec 100644 --- a/vendor/github.com/containers/common/libnetwork/netavark/network.go +++ b/vendor/github.com/containers/common/libnetwork/netavark/network.go @@ -231,7 +231,9 @@ func parseNetwork(network *types.Network) error { return errors.Errorf("invalid network ID %q", network.ID) } - return util.ValidateSubnets(network, nil) + // add gatway when not internal or dns enabled + addGateway := !network.Internal || network.DNSEnabled + return util.ValidateSubnets(network, addGateway, nil) } func (n *netavarkNetwork) createDefaultNetwork() (*types.Network, error) { diff --git a/vendor/github.com/containers/common/pkg/auth/auth.go b/vendor/github.com/containers/common/pkg/auth/auth.go index ff52b028e..af3c8f803 100644 --- a/vendor/github.com/containers/common/pkg/auth/auth.go +++ b/vendor/github.com/containers/common/pkg/auth/auth.go @@ -248,7 +248,7 @@ func getUserAndPass(opts *LoginOptions, password, userFromAuthFile string) (user } if password == "" { fmt.Fprint(opts.Stdout, "Password: ") - pass, err := terminal.ReadPassword(0) + pass, err := terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { return "", "", errors.Wrap(err, "reading password") } diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index 5ab8cd7f2..eac64b077 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.47.3" +const Version = "0.47.4" diff --git a/vendor/github.com/coreos/stream-metadata-go/release/release.go b/vendor/github.com/coreos/stream-metadata-go/release/release.go new file mode 100644 index 000000000..84a032703 --- /dev/null +++ b/vendor/github.com/coreos/stream-metadata-go/release/release.go @@ -0,0 +1,112 @@ +// Package release contains APIs for interacting with a +// particular "release". Avoid this unless you are sure +// you need it. It's expected that CoreOS users interact +// with streams instead. +package release + +import ( + relrhcos "github.com/coreos/stream-metadata-go/release/rhcos" +) + +// Index models the release index: +// https://github.com/coreos/fedora-coreos-tracker/tree/master/metadata/release-index +type Index struct { + Note string `json:"note"` // used to note to users not to consume the release metadata index + Releases []IndexRelease `json:"releases"` + Metadata Metadata `json:"metadata"` + Stream string `json:"stream"` +} + +// IndexRelease is a "release pointer" from a release index +type IndexRelease struct { + Commits []IndexReleaseCommit `json:"commits"` + Version string `json:"version"` + MetadataURL string `json:"metadata"` +} + +// IndexReleaseCommit describes an ostree commit plus architecture +type IndexReleaseCommit struct { + Architecture string `json:"architecture"` + Checksum string `json:"checksum"` +} + +// Release contains details from release.json +type Release struct { + Release string `json:"release"` + Stream string `json:"stream"` + Metadata Metadata `json:"metadata"` + Architectures map[string]Arch `json:"architectures"` +} + +// Metadata is common metadata that contains last-modified +type Metadata struct { + LastModified string `json:"last-modified"` +} + +// Arch release details +type Arch struct { + Commit string `json:"commit"` + Media Media `json:"media"` + RHELCoreOSExtensions *relrhcos.Extensions `json:"rhel-coreos-extensions,omitempty"` +} + +// Media contains release details for various platforms +type Media struct { + Aliyun *PlatformBase `json:"aliyun"` + Aws *PlatformAws `json:"aws"` + Azure *PlatformBase `json:"azure"` + Digitalocean *PlatformBase `json:"digitalocean"` + Exoscale *PlatformBase `json:"exoscale"` + Gcp *PlatformGcp `json:"gcp"` + Ibmcloud *PlatformBase `json:"ibmcloud"` + Metal *PlatformBase `json:"metal"` + Openstack *PlatformBase `json:"openstack"` + Qemu *PlatformBase `json:"qemu"` + Vmware *PlatformBase `json:"vmware"` + Vultr *PlatformBase `json:"vultr"` +} + +// PlatformBase with no cloud images +type PlatformBase struct { + Artifacts map[string]ImageFormat `json:"artifacts"` +} + +// PlatformAws contains AWS image information +type PlatformAws struct { + PlatformBase + Images map[string]CloudImage `json:"images"` +} + +// PlatformGcp GCP image detail +type PlatformGcp struct { + PlatformBase + Image *GcpImage `json:"image"` +} + +// ImageFormat contains all artifacts for a single OS image +type ImageFormat struct { + Disk *Artifact `json:"disk,omitempty"` + Kernel *Artifact `json:"kernel,omitempty"` + Initramfs *Artifact `json:"initramfs,omitempty"` + Rootfs *Artifact `json:"rootfs,omitempty"` +} + +// Artifact represents one image file, plus its metadata +type Artifact struct { + Location string `json:"location"` + Signature string `json:"signature"` + Sha256 string `json:"sha256"` + UncompressedSha256 string `json:"uncompressed-sha256,omitempty"` +} + +// CloudImage generic image detail +type CloudImage struct { + Image string `json:"image"` +} + +// GcpImage represents a GCP cloud image +type GcpImage struct { + Project string `json:"project,omitempty"` + Family string `json:"family,omitempty"` + Name string `json:"name,omitempty"` +} diff --git a/vendor/github.com/coreos/stream-metadata-go/release/rhcos/rhcos.go b/vendor/github.com/coreos/stream-metadata-go/release/rhcos/rhcos.go new file mode 100644 index 000000000..aeae2c8be --- /dev/null +++ b/vendor/github.com/coreos/stream-metadata-go/release/rhcos/rhcos.go @@ -0,0 +1,14 @@ +package rhcos + +// Extensions is data specific to Red Hat Enterprise Linux CoreOS +type Extensions struct { + AzureDisk *AzureDisk `json:"azure-disk,omitempty"` +} + +// AzureDisk represents an Azure cloud image. +type AzureDisk struct { + // URL to an image already stored in Azure infrastructure + // that can be copied into an image gallery. Avoid creating VMs directly + // from this URL as that may lead to performance limitations. + URL string `json:"url,omitempty"` +} diff --git a/vendor/github.com/coreos/stream-metadata-go/release/translate.go b/vendor/github.com/coreos/stream-metadata-go/release/translate.go new file mode 100644 index 000000000..518c75eb9 --- /dev/null +++ b/vendor/github.com/coreos/stream-metadata-go/release/translate.go @@ -0,0 +1,196 @@ +package release + +import ( + "github.com/coreos/stream-metadata-go/stream" + "github.com/coreos/stream-metadata-go/stream/rhcos" +) + +func mapArtifact(ra *Artifact) *stream.Artifact { + if ra == nil { + return nil + } + return &stream.Artifact{ + Location: ra.Location, + Signature: ra.Signature, + Sha256: ra.Sha256, + UncompressedSha256: ra.UncompressedSha256, + } +} + +func mapFormats(m map[string]ImageFormat) map[string]stream.ImageFormat { + r := make(map[string]stream.ImageFormat) + for k, v := range m { + r[k] = stream.ImageFormat{ + Disk: mapArtifact(v.Disk), + Kernel: mapArtifact(v.Kernel), + Initramfs: mapArtifact(v.Initramfs), + Rootfs: mapArtifact(v.Rootfs), + } + } + return r +} + +// Convert a release architecture to a stream architecture +func (releaseArch *Arch) toStreamArch(rel *Release) stream.Arch { + artifacts := make(map[string]stream.PlatformArtifacts) + cloudImages := stream.Images{} + var rhcosExt *rhcos.Extensions + relRHCOSExt := releaseArch.RHELCoreOSExtensions + if relRHCOSExt != nil { + rhcosExt = &rhcos.Extensions{} + } + if releaseArch.Media.Aws != nil { + artifacts["aws"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Aws.Artifacts), + } + awsAmis := stream.AwsImage{ + Regions: make(map[string]stream.AwsRegionImage), + } + if releaseArch.Media.Aws.Images != nil { + for region, ami := range releaseArch.Media.Aws.Images { + ri := stream.AwsRegionImage{Release: rel.Release, Image: ami.Image} + awsAmis.Regions[region] = ri + + } + cloudImages.Aws = &awsAmis + } + } + + if releaseArch.Media.Azure != nil { + artifacts["azure"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Azure.Artifacts), + } + + if relRHCOSExt != nil { + az := relRHCOSExt.AzureDisk + if az != nil { + rhcosExt.AzureDisk = &rhcos.AzureDisk{ + Release: rel.Release, + URL: az.URL, + } + } + } + // In the future this is where we'd also add FCOS Marketplace data. + // See https://github.com/coreos/stream-metadata-go/issues/13 + } + + if releaseArch.Media.Aliyun != nil { + artifacts["aliyun"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Aliyun.Artifacts), + } + } + + if releaseArch.Media.Exoscale != nil { + artifacts["exoscale"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Exoscale.Artifacts), + } + } + + if releaseArch.Media.Vultr != nil { + artifacts["vultr"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Vultr.Artifacts), + } + } + + if releaseArch.Media.Gcp != nil { + artifacts["gcp"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Gcp.Artifacts), + } + + if releaseArch.Media.Gcp.Image != nil { + cloudImages.Gcp = &stream.GcpImage{ + Name: releaseArch.Media.Gcp.Image.Name, + Family: releaseArch.Media.Gcp.Image.Family, + Project: releaseArch.Media.Gcp.Image.Project, + } + } + } + + if releaseArch.Media.Digitalocean != nil { + artifacts["digitalocean"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Digitalocean.Artifacts), + } + + /* We're producing artifacts but they're not yet available + in DigitalOcean as distribution images. + digitalOceanImage := stream.CloudImage{Image: fmt.Sprintf("fedora-coreos-%s", Stream)} + cloudImages.Digitalocean = &digitalOceanImage + */ + } + + if releaseArch.Media.Ibmcloud != nil { + artifacts["ibmcloud"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Ibmcloud.Artifacts), + } + } + + // if releaseArch.Media.Packet != nil { + // packet := StreamMediaDetails{ + // Release: rel.Release, + // Formats: releaseArch.Media.Packet.Artifacts, + // } + // artifacts.Packet = &packet + + // packetImage := StreamCloudImage{Image: fmt.Sprintf("fedora_coreos_%s", rel.Stream)} + // cloudImages.Packet = &packetImage + // } + + if releaseArch.Media.Openstack != nil { + artifacts["openstack"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Openstack.Artifacts), + } + } + + if releaseArch.Media.Qemu != nil { + artifacts["qemu"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Qemu.Artifacts), + } + } + + // if releaseArch.Media.Virtualbox != nil { + // virtualbox := StreamMediaDetails{ + // Release: rel.Release, + // Formats: releaseArch.Media.Virtualbox.Artifacts, + // } + // artifacts.Virtualbox = &virtualbox + // } + + if releaseArch.Media.Vmware != nil { + artifacts["vmware"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Vmware.Artifacts), + } + } + + if releaseArch.Media.Metal != nil { + artifacts["metal"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Metal.Artifacts), + } + } + + return stream.Arch{ + Artifacts: artifacts, + Images: cloudImages, + RHELCoreOSExtensions: rhcosExt, + } +} + +// ToStreamArchitectures converts a release to a stream +func (rel *Release) ToStreamArchitectures() map[string]stream.Arch { + streamArch := make(map[string]stream.Arch) + for arch, releaseArch := range rel.Architectures { + streamArch[arch] = releaseArch.toStreamArch(rel) + } + return streamArch +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 909c2707e..f6042a041 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -109,7 +109,7 @@ github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/util -# github.com/containers/common v0.47.3 +# github.com/containers/common v0.47.4 ## explicit github.com/containers/common/libimage github.com/containers/common/libimage/manifests @@ -291,6 +291,8 @@ github.com/coreos/go-systemd/v22/sdjournal ## explicit github.com/coreos/stream-metadata-go/fedoracoreos github.com/coreos/stream-metadata-go/fedoracoreos/internals +github.com/coreos/stream-metadata-go/release +github.com/coreos/stream-metadata-go/release/rhcos github.com/coreos/stream-metadata-go/stream github.com/coreos/stream-metadata-go/stream/rhcos # github.com/cyphar/filepath-securejoin v0.2.3 |