diff options
Diffstat (limited to 'pkg')
32 files changed, 971 insertions, 156 deletions
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 15ba5c685..fd310711f 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -13,7 +13,7 @@ import ( "time" "github.com/containers/buildah" - "github.com/containers/buildah/imagebuildah" + buildahDefine "github.com/containers/buildah/define" "github.com/containers/buildah/util" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" @@ -98,6 +98,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { OutputFormat string `schema:"outputformat"` Platform string `schema:"platform"` Pull bool `schema:"pull"` + PullPolicy string `schema:"pullpolicy"` Quiet bool `schema:"q"` Registry string `schema:"registry"` Rm bool `schema:"rm"` @@ -275,10 +276,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { jobs = query.Jobs } - pullPolicy := buildah.PullIfMissing - if _, found := r.URL.Query()["pull"]; found { - if query.Pull { - pullPolicy = buildah.PullAlways + pullPolicy := buildahDefine.PullIfMissing + if utils.IsLibpodRequest(r) { + pullPolicy = buildahDefine.PolicyMap[query.PullPolicy] + } else { + if _, found := r.URL.Query()["pull"]; found { + if query.Pull { + pullPolicy = buildahDefine.PullAlways + } } } @@ -309,7 +314,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - buildOptions := imagebuildah.BuildOptions{ + buildOptions := buildahDefine.BuildOptions{ AddCapabilities: addCaps, AdditionalTags: additionalTags, Annotations: annotations, diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go index fae147440..f1cd77a9a 100644 --- a/pkg/api/handlers/compat/version.go +++ b/pkg/api/handlers/compat/version.go @@ -10,8 +10,8 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/domain/entities/types" "github.com/containers/podman/v3/version" - docker "github.com/docker/docker/api/types" "github.com/pkg/errors" ) @@ -32,7 +32,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { return } - components := []docker.ComponentVersion{{ + components := []types.ComponentVersion{{ Name: "Podman Engine", Version: versionInfo.Version, Details: map[string]string{ @@ -52,7 +52,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { minVersion := version.APIVersion[version.Compat][version.MinimalAPI] utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{ - Version: docker.Version{ + Version: types.Version{ Platform: struct { Name string }{ diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index f5eaf6f6d..736203171 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -28,10 +28,6 @@ type ContainerConfig struct { dockerContainer.Config } -type LibpodImagesLoadReport struct { - ID string `json:"id"` -} - type LibpodImagesPullReport struct { entities.ImagePullReport } diff --git a/pkg/api/handlers/types/types.go b/pkg/api/handlers/types/types.go new file mode 100644 index 000000000..71165364f --- /dev/null +++ b/pkg/api/handlers/types/types.go @@ -0,0 +1,23 @@ +package types + +import ( + "github.com/containers/podman/v3/pkg/domain/entities" +) + +// LibpodImagesRemoveReport is the return type for image removal via the rest +// api. +type LibpodImagesRemoveReport struct { + entities.ImageRemoveReport + // Image removal requires is to return data and an error. + Errors []string +} + +// HistoryResponse provides details on image layers +type HistoryResponse struct { + ID string `json:"Id"` + Created int64 + CreatedBy string + Tags []string + Size int64 + Comment string +} diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index 12fd083bb..d282edf23 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -205,7 +205,7 @@ type swagHealthCheckRunResponse struct { type swagVersion struct { // in:body Body struct { - entities.SystemVersionReport + entities.ComponentVersion } } diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 9d77883f9..c79d79136 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -15,7 +15,6 @@ import ( "strconv" "strings" - "github.com/containers/buildah" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/domain/entities" @@ -175,9 +174,8 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO if len(platform) > 0 { params.Set("platform", platform) } - if options.PullPolicy == buildah.PullAlways { - params.Set("pull", "1") - } + params.Set("pullpolicy", options.PullPolicy.String()) + if options.Quiet { params.Set("q", "1") } diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 1be2bdfdd..8680d6baa 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -8,7 +8,7 @@ import ( "net/url" "strconv" - "github.com/containers/podman/v3/pkg/api/handlers" + "github.com/containers/podman/v3/pkg/api/handlers/types" "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/domain/entities" @@ -96,12 +96,12 @@ func Tree(ctx context.Context, nameOrID string, options *TreeOptions) (*entities } // History returns the parent layers of an image. -func History(ctx context.Context, nameOrID string, options *HistoryOptions) ([]*handlers.HistoryResponse, error) { +func History(ctx context.Context, nameOrID string, options *HistoryOptions) ([]*types.HistoryResponse, error) { if options == nil { options = new(HistoryOptions) } _ = options - var history []*handlers.HistoryResponse + var history []*types.HistoryResponse conn, err := bindings.GetClient(ctx) if err != nil { return nil, err diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go index beecce7bf..e45e583f4 100644 --- a/pkg/bindings/images/rm.go +++ b/pkg/bindings/images/rm.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - "github.com/containers/podman/v3/pkg/api/handlers" + "github.com/containers/podman/v3/pkg/api/handlers/types" "github.com/containers/podman/v3/pkg/bindings" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/errorhandling" @@ -19,7 +19,7 @@ func Remove(ctx context.Context, images []string, options *RemoveOptions) (*enti // FIXME - bindings tests are missing for this endpoint. Once the CI is // re-enabled for bindings, we need to add them. At the time of writing, // the tests don't compile. - var report handlers.LibpodImagesRemoveReport + var report types.LibpodImagesRemoveReport conn, err := bindings.GetClient(ctx) if err != nil { return nil, []error{err} diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 7bf70c82b..1f3e46729 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -1,7 +1,7 @@ package images import ( - "github.com/containers/buildah/imagebuildah" + buildahDefine "github.com/containers/buildah/define" ) //go:generate go run ../generator/generator.go RemoveOptions @@ -162,7 +162,7 @@ type PullOptions struct { //BuildOptions are optional options for building images type BuildOptions struct { - imagebuildah.BuildOptions + buildahDefine.BuildOptions } //go:generate go run ../generator/generator.go ExistsOptions diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 7999d8209..3cc46ed0a 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -8,33 +8,32 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/podman/v3/pkg/inspect" "github.com/containers/podman/v3/pkg/trust" - docker "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) type Image struct { - ID string `json:"Id"` - RepoTags []string `json:",omitempty"` - RepoDigests []string `json:",omitempty"` - Parent string `json:",omitempty"` - Comment string `json:",omitempty"` - Created string `json:",omitempty"` - Container string `json:",omitempty"` - ContainerConfig *container.Config `json:",omitempty"` - DockerVersion string `json:",omitempty"` - Author string `json:",omitempty"` - Config *container.Config `json:",omitempty"` - Architecture string `json:",omitempty"` - Variant string `json:",omitempty"` - Os string `json:",omitempty"` - OsVersion string `json:",omitempty"` - Size int64 `json:",omitempty"` - VirtualSize int64 `json:",omitempty"` - GraphDriver docker.GraphDriverData `json:",omitempty"` - RootFS docker.RootFS `json:",omitempty"` - Metadata docker.ImageMetadata `json:",omitempty"` + ID string `json:"Id"` + RepoTags []string `json:",omitempty"` + RepoDigests []string `json:",omitempty"` + Parent string `json:",omitempty"` + Comment string `json:",omitempty"` + Created string `json:",omitempty"` + Container string `json:",omitempty"` + ContainerConfig *container.Config `json:",omitempty"` + DockerVersion string `json:",omitempty"` + Author string `json:",omitempty"` + Config *container.Config `json:",omitempty"` + Architecture string `json:",omitempty"` + Variant string `json:",omitempty"` + Os string `json:",omitempty"` + OsVersion string `json:",omitempty"` + Size int64 `json:",omitempty"` + VirtualSize int64 `json:",omitempty"` + GraphDriver string `json:",omitempty"` + RootFS string `json:",omitempty"` + Metadata string `json:",omitempty"` // Podman extensions Digest digest.Digest `json:",omitempty"` diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index 4b8383613..1a671d59e 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -5,7 +5,7 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities/reports" - "github.com/docker/docker/api/types" + "github.com/containers/podman/v3/pkg/domain/entities/types" "github.com/spf13/cobra" ) diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index 9efc64c93..02e374111 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -3,7 +3,7 @@ package entities import ( "net" - "github.com/containers/buildah/imagebuildah" + buildahDefine "github.com/containers/buildah/define" "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/storage/pkg/archive" @@ -91,7 +91,7 @@ type ContainerCreateResponse struct { // BuildOptions describe the options for building container images. type BuildOptions struct { - imagebuildah.BuildOptions + buildahDefine.BuildOptions } // BuildReport is the image-build report. diff --git a/pkg/domain/entities/types/auth.go b/pkg/domain/entities/types/auth.go new file mode 100644 index 000000000..ddf15bb18 --- /dev/null +++ b/pkg/domain/entities/types/auth.go @@ -0,0 +1,22 @@ +package types // import "github.com/docker/docker/api/types" + +// AuthConfig contains authorization information for connecting to a Registry +type AuthConfig struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth,omitempty"` + + // Email is an optional value associated with the username. + // This field is deprecated and will be removed in a later + // version of docker. + Email string `json:"email,omitempty"` + + ServerAddress string `json:"serveraddress,omitempty"` + + // IdentityToken is used to authenticate the user and get + // an access token for the registry. + IdentityToken string `json:"identitytoken,omitempty"` + + // RegistryToken is a bearer token to be sent to a registry + RegistryToken string `json:"registrytoken,omitempty"` +} diff --git a/pkg/domain/entities/types/types.go b/pkg/domain/entities/types/types.go new file mode 100644 index 000000000..77834c0cb --- /dev/null +++ b/pkg/domain/entities/types/types.go @@ -0,0 +1,28 @@ +package types // import "github.com/docker/docker/api/types" + +// ComponentVersion describes the version information for a specific component. +type ComponentVersion struct { + Name string + Version string + Details map[string]string `json:",omitempty"` +} + +// Version contains response of Engine API: +// GET "/version" +type Version struct { + Platform struct{ Name string } `json:",omitempty"` + Components []ComponentVersion `json:",omitempty"` + + // The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility + + Version string + APIVersion string `json:"ApiVersion"` + MinAPIVersion string `json:"MinAPIVersion,omitempty"` + GitCommit string + GoVersion string + Os string + Arch string + KernelVersion string `json:",omitempty"` + Experimental bool `json:",omitempty"` + BuildTime string `json:",omitempty"` +} diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go index beb2a75ac..55a6a1b14 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -4,10 +4,72 @@ import ( "net/url" "github.com/containers/podman/v3/libpod/define" - docker_api_types "github.com/docker/docker/api/types" - docker_api_types_volume "github.com/docker/docker/api/types/volume" ) +// Volume volume +// swagger:model Volume +type volume struct { + + // Date/Time the volume was created. + CreatedAt string `json:"CreatedAt,omitempty"` + + // Name of the volume driver used by the volume. + // Required: true + Driver string `json:"Driver"` + + // User-defined key/value metadata. + // Required: true + Labels map[string]string `json:"Labels"` + + // Mount path of the volume on the host. + // Required: true + Mountpoint string `json:"Mountpoint"` + + // Name of the volume. + // Required: true + Name string `json:"Name"` + + // The driver specific options used when creating the volume. + // + // Required: true + Options map[string]string `json:"Options"` + + // The level at which the volume exists. Either `global` for cluster-wide, + // or `local` for machine level. + // + // Required: true + Scope string `json:"Scope"` + + // Low-level details about the volume, provided by the volume driver. + // Details are returned as a map with key/value pairs: + // `{"key":"value","key2":"value2"}`. + // + // The `Status` field is optional, and is omitted if the volume driver + // does not support this feature. + // + Status map[string]interface{} `json:"Status,omitempty"` + + // usage data + UsageData *VolumeUsageData `json:"UsageData,omitempty"` +} + +type VolumeUsageData struct { + + // The number of containers referencing this volume. This field + // is set to `-1` if the reference-count is not available. + // + // Required: true + RefCount int64 `json:"RefCount"` + + // Amount of disk space used by the volume (in bytes). This information + // is only available for volumes created with the `"local"` volume + // driver. For volumes created with other volume drivers, this field + // is set to `-1` ("not available") + // + // Required: true + Size int64 `json:"Size"` +} + // swagger:model VolumeCreate type VolumeCreateOptions struct { // New volume's name. Can be left blank @@ -113,14 +175,14 @@ type SwagVolumeListResponse struct { */ // swagger:model DockerVolumeCreate -type DockerVolumeCreate docker_api_types_volume.VolumeCreateBody +type DockerVolumeCreate VolumeCreateBody // This response definition is used for both the create and inspect endpoints // swagger:response DockerVolumeInfoResponse type SwagDockerVolumeInfoResponse struct { // in:body Body struct { - docker_api_types.Volume + volume } } @@ -129,6 +191,30 @@ type SwagDockerVolumeInfoResponse struct { type SwagDockerVolumePruneResponse struct { // in:body Body struct { - docker_api_types.VolumesPruneReport + // docker_api_types.VolumesPruneReport } } + +// VolumeCreateBody Volume configuration +// swagger:model VolumeCreateBody +type VolumeCreateBody struct { + + // Name of the volume driver to use. + // Required: true + Driver string `json:"Driver"` + + // A mapping of driver options and values. These options are + // passed directly to the driver and are driver specific. + // + // Required: true + DriverOpts map[string]string `json:"DriverOpts"` + + // User-defined key/value metadata. + // Required: true + Labels map[string]string `json:"Labels"` + + // The new volume's name. If not specified, Docker generates a name. + // + // Required: true + Name string `json:"Name"` +} diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 7d87fc83a..3b5c141d7 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -9,6 +9,7 @@ import ( "os" "strings" + "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" @@ -135,6 +136,12 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY report entities.PlayKubeReport ) + // Create the secret manager before hand + secretsManager, err := secrets.NewManager(ic.Libpod.GetSecretsStorageDir()) + if err != nil { + return nil, err + } + // check for name collision between pod and container if podName == "" { return nil, errors.Errorf("pod does not have a name") @@ -261,16 +268,17 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } specgenOpts := kube.CtrSpecGenOptions{ - Container: container, - Image: newImage, - Volumes: volumes, - PodID: pod.ID(), - PodName: podName, - PodInfraID: podInfraID, - ConfigMaps: configMaps, - SeccompPaths: seccompPaths, - RestartPolicy: ctrRestartPolicy, - NetNSIsHost: p.NetNS.IsHost(), + Container: container, + Image: newImage, + Volumes: volumes, + PodID: pod.ID(), + PodName: podName, + PodInfraID: podInfraID, + ConfigMaps: configMaps, + SeccompPaths: seccompPaths, + RestartPolicy: ctrRestartPolicy, + NetNSIsHost: p.NetNS.IsHost(), + SecretsManager: secretsManager, } specGen, err := kube.ToSpecGen(ctx, &specgenOpts) if err != nil { diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 9f7c8919b..a3e753384 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -67,7 +67,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) if os.Geteuid() == 0 { ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup() if err != nil { - logrus.Warnf("Failed to detect the owner for the current cgroup: %v", err) + logrus.Infof("Failed to detect the owner for the current cgroup: %v", err) } if !ownsCgroup { conf, err := ic.Config(context.Background()) diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index 8b6581c7b..b0d9dc797 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -15,8 +15,8 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/namespaces" "github.com/containers/podman/v3/pkg/rootless" - "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/types" "github.com/pkg/errors" "github.com/sirupsen/logrus" flag "github.com/spf13/pflag" @@ -100,7 +100,7 @@ func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg *entities.Podm func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} - storageOpts := storage.StoreOptions{} + storageOpts := types.StoreOptions{} cfg := opts.config storageSet := false @@ -237,8 +237,8 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo } // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping -func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) { - options := storage.IDMappingOptions{ +func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*types.IDMappingOptions, error) { + options := types.IDMappingOptions{ HostUIDMapping: true, HostGIDMapping: true, } diff --git a/pkg/machine/qemu/options_linux_amd64.go b/pkg/machine/qemu/options_linux_amd64.go index cc0a4bab2..3edd97ea1 100644 --- a/pkg/machine/qemu/options_linux_amd64.go +++ b/pkg/machine/qemu/options_linux_amd64.go @@ -1,11 +1,14 @@ package qemu var ( - QemuCommand = "qemu-kvm" + QemuCommand = "qemu-system-x86_64" ) func (v *MachineVM) addArchOptions() []string { - opts := []string{"-cpu", "host"} + opts := []string{ + "-accel", "kvm", + "-cpu", "host", + } return opts } diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index c35f68e02..a7736aee0 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/containers/storage" + "github.com/containers/storage/types" ) const ( @@ -109,12 +109,12 @@ func (n UsernsMode) IsDefaultValue() bool { // GetAutoOptions returns a AutoUserNsOptions with the settings to setup automatically // a user namespace. -func (n UsernsMode) GetAutoOptions() (*storage.AutoUserNsOptions, error) { +func (n UsernsMode) GetAutoOptions() (*types.AutoUserNsOptions, error) { parts := strings.SplitN(string(n), ":", 2) if parts[0] != "auto" { return nil, fmt.Errorf("wrong user namespace mode") } - options := storage.AutoUserNsOptions{} + options := types.AutoUserNsOptions{} if len(parts) == 1 { return &options, nil } @@ -131,13 +131,13 @@ func (n UsernsMode) GetAutoOptions() (*storage.AutoUserNsOptions, error) { } options.Size = uint32(s) case "uidmapping": - mapping, err := storage.ParseIDMapping([]string{v[1]}, nil, "", "") + mapping, err := types.ParseIDMapping([]string{v[1]}, nil, "", "") if err != nil { return nil, err } options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping.UIDMap...) case "gidmapping": - mapping, err := storage.ParseIDMapping(nil, []string{v[1]}, "", "") + mapping, err := types.ParseIDMapping(nil, []string{v[1]}, "", "") if err != nil { return nil, err } diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go index b5538efc3..0b9d719a9 100644 --- a/pkg/rootless/rootless.go +++ b/pkg/rootless/rootless.go @@ -5,7 +5,7 @@ import ( "sort" "sync" - "github.com/containers/storage" + "github.com/containers/storage/pkg/lockfile" "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -25,7 +25,7 @@ func TryJoinPauseProcess(pausePidPath string) (bool, int, error) { } // It could not join the pause process, let's lock the file before trying to delete it. - pidFileLock, err := storage.GetLockfile(pausePidPath) + pidFileLock, err := lockfile.GetLockfile(pausePidPath) if err != nil { // The file was deleted by another process. if os.IsNotExist(err) { diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 03697b353..1d724ffb0 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -11,7 +11,7 @@ import ( "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" - "github.com/containers/storage" + "github.com/containers/storage/types" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -367,7 +367,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. return options, nil } -func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Config, syslog, rm, exec bool) ([]string, error) { +func CreateExitCommandArgs(storageConfig types.StoreOptions, config *config.Config, syslog, rm, exec bool) ([]string, error) { // We need a cleanup process for containers in the current model. // But we can't assume that the caller is Podman - it could be another // user of the API. diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index d61c8bd19..31ed3fd7c 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -2,11 +2,13 @@ package kube import ( "context" + "encoding/json" "fmt" "net" "strings" "github.com/containers/common/pkg/parse" + "github.com/containers/common/pkg/secrets" "github.com/containers/podman/v3/libpod/image" ann "github.com/containers/podman/v3/pkg/annotations" "github.com/containers/podman/v3/pkg/specgen" @@ -94,6 +96,8 @@ type CtrSpecGenOptions struct { RestartPolicy string // NetNSIsHost tells the container to use the host netns NetNSIsHost bool + // SecretManager to access the secrets + SecretsManager *secrets.SecretsManager } func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGenerator, error) { @@ -210,12 +214,18 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener } for _, env := range opts.Container.Env { - value := envVarValue(env, opts.ConfigMaps) + value, err := envVarValue(env, opts) + if err != nil { + return nil, err + } envs[env.Name] = value } for _, envFrom := range opts.Container.EnvFrom { - cmEnvs := envVarsFromConfigMap(envFrom, opts.ConfigMaps) + cmEnvs, err := envVarsFrom(envFrom, opts) + if err != nil { + return nil, err + } for k, v := range cmEnvs { envs[k] = v @@ -325,40 +335,96 @@ func quantityToInt64(quantity *resource.Quantity) (int64, error) { return 0, errors.Errorf("Quantity cannot be represented as int64: %v", quantity) } -// envVarsFromConfigMap returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container -func envVarsFromConfigMap(envFrom v1.EnvFromSource, configMaps []v1.ConfigMap) map[string]string { +// read a k8s secret in JSON format from the secret manager +func k8sSecretFromSecretManager(name string, secretsManager *secrets.SecretsManager) (map[string][]byte, error) { + _, jsonSecret, err := secretsManager.LookupSecretData(name) + if err != nil { + return nil, err + } + + var secrets map[string][]byte + if err := json.Unmarshal(jsonSecret, &secrets); err != nil { + return nil, errors.Errorf("Secret %v is not valid JSON: %v", name, err) + } + return secrets, nil +} + +// envVarsFrom returns all key-value pairs as env vars from a configMap or secret that matches the envFrom setting of a container +func envVarsFrom(envFrom v1.EnvFromSource, opts *CtrSpecGenOptions) (map[string]string, error) { envs := map[string]string{} if envFrom.ConfigMapRef != nil { - cmName := envFrom.ConfigMapRef.Name + cmRef := envFrom.ConfigMapRef + err := errors.Errorf("Configmap %v not found", cmRef.Name) - for _, c := range configMaps { - if cmName == c.Name { + for _, c := range opts.ConfigMaps { + if cmRef.Name == c.Name { envs = c.Data + err = nil break } } + + if err != nil && (cmRef.Optional == nil || !*cmRef.Optional) { + return nil, err + } } - return envs + if envFrom.SecretRef != nil { + secRef := envFrom.SecretRef + secret, err := k8sSecretFromSecretManager(secRef.Name, opts.SecretsManager) + if err == nil { + for k, v := range secret { + envs[k] = string(v) + } + } else if secRef.Optional == nil || !*secRef.Optional { + return nil, err + } + } + + return envs, nil } // envVarValue returns the environment variable value configured within the container's env setting. -// It gets the value from a configMap if specified, otherwise returns env.Value -func envVarValue(env v1.EnvVar, configMaps []v1.ConfigMap) string { - for _, c := range configMaps { - if env.ValueFrom != nil { - if env.ValueFrom.ConfigMapKeyRef != nil { - if env.ValueFrom.ConfigMapKeyRef.Name == c.Name { - if value, ok := c.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok { - return value +// It gets the value from a configMap or secret if specified, otherwise returns env.Value +func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) { + if env.ValueFrom != nil { + if env.ValueFrom.ConfigMapKeyRef != nil { + cmKeyRef := env.ValueFrom.ConfigMapKeyRef + err := errors.Errorf("Cannot set env %v: configmap %v not found", env.Name, cmKeyRef.Name) + + for _, c := range opts.ConfigMaps { + if cmKeyRef.Name == c.Name { + if value, ok := c.Data[cmKeyRef.Key]; ok { + return value, nil } + err = errors.Errorf("Cannot set env %v: key %s not found in configmap %v", env.Name, cmKeyRef.Key, cmKeyRef.Name) + break + } + } + if cmKeyRef.Optional == nil || !*cmKeyRef.Optional { + return "", err + } + return "", nil + } + + if env.ValueFrom.SecretKeyRef != nil { + secKeyRef := env.ValueFrom.SecretKeyRef + secret, err := k8sSecretFromSecretManager(secKeyRef.Name, opts.SecretsManager) + if err == nil { + if val, ok := secret[secKeyRef.Key]; ok { + return string(val), nil } + err = errors.Errorf("Secret %v has not %v key", secKeyRef.Name, secKeyRef.Key) + } + if secKeyRef.Optional == nil || !*secKeyRef.Optional { + return "", errors.Errorf("Cannot set env %v: %v", env.Name, err) } + return "", nil } } - return env.Value + return env.Value, nil } // getPodPorts converts a slice of kube container descriptions to an diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go index 148540e9f..f714826f0 100644 --- a/pkg/specgen/generate/kube/play_test.go +++ b/pkg/specgen/generate/kube/play_test.go @@ -1,19 +1,49 @@ package kube import ( + "encoding/json" + "io/ioutil" + "os" "testing" + "github.com/containers/common/pkg/secrets" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" v12 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func TestEnvVarsFromConfigMap(t *testing.T) { +func createSecrets(t *testing.T, d string) *secrets.SecretsManager { + secretsManager, err := secrets.NewManager(d) + assert.NoError(t, err) + + driver := "file" + driverOpts := map[string]string{ + "path": d, + } + + for _, s := range k8sSecrets { + data, err := json.Marshal(s.Data) + assert.NoError(t, err) + + _, err = secretsManager.Store(s.ObjectMeta.Name, data, driver, driverOpts) + assert.NoError(t, err) + } + + return secretsManager +} + +func TestEnvVarsFrom(t *testing.T) { + d, err := ioutil.TempDir("", "secrets") + assert.NoError(t, err) + defer os.RemoveAll(d) + secretsManager := createSecrets(t, d) + tests := []struct { - name string - envFrom v1.EnvFromSource - configMapList []v1.ConfigMap - expected map[string]string + name string + envFrom v1.EnvFromSource + options CtrSpecGenOptions + succeed bool + expected map[string]string }{ { "ConfigMapExists", @@ -24,7 +54,10 @@ func TestEnvVarsFromConfigMap(t *testing.T) { }, }, }, - configMapList, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + true, map[string]string{ "myvar": "foo", }, @@ -38,7 +71,26 @@ func TestEnvVarsFromConfigMap(t *testing.T) { }, }, }, - configMapList, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + false, + nil, + }, + { + "OptionalConfigMapDoesNotExist", + v1.EnvFromSource{ + ConfigMapRef: &v1.ConfigMapEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + Optional: &optional, + }, + }, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + true, map[string]string{}, }, { @@ -50,7 +102,74 @@ func TestEnvVarsFromConfigMap(t *testing.T) { }, }, }, - []v1.ConfigMap{}, + CtrSpecGenOptions{ + ConfigMaps: []v1.ConfigMap{}, + }, + false, + nil, + }, + { + "OptionalEmptyConfigMapList", + v1.EnvFromSource{ + ConfigMapRef: &v1.ConfigMapEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Optional: &optional, + }, + }, + CtrSpecGenOptions{ + ConfigMaps: []v1.ConfigMap{}, + }, + true, + map[string]string{}, + }, + { + "SecretExists", + v1.EnvFromSource{ + SecretRef: &v1.SecretEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + true, + map[string]string{ + "myvar": "foo", + }, + }, + { + "SecretDoesNotExist", + v1.EnvFromSource{ + SecretRef: &v1.SecretEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + false, + nil, + }, + { + "OptionalSecretDoesNotExist", + v1.EnvFromSource{ + SecretRef: &v1.SecretEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + Optional: &optional, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + true, map[string]string{}, }, } @@ -58,18 +177,25 @@ func TestEnvVarsFromConfigMap(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - result := envVarsFromConfigMap(test.envFrom, test.configMapList) + result, err := envVarsFrom(test.envFrom, &test.options) + assert.Equal(t, err == nil, test.succeed) assert.Equal(t, test.expected, result) }) } } func TestEnvVarValue(t *testing.T) { + d, err := ioutil.TempDir("", "secrets") + assert.NoError(t, err) + defer os.RemoveAll(d) + secretsManager := createSecrets(t, d) + tests := []struct { - name string - envVar v1.EnvVar - configMapList []v1.ConfigMap - expected string + name string + envVar v1.EnvVar + options CtrSpecGenOptions + succeed bool + expected string }{ { "ConfigMapExists", @@ -84,7 +210,10 @@ func TestEnvVarValue(t *testing.T) { }, }, }, - configMapList, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + true, "foo", }, { @@ -100,7 +229,30 @@ func TestEnvVarValue(t *testing.T) { }, }, }, - configMapList, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + false, + "", + }, + { + "OptionalContainerKeyDoesNotExistInConfigMap", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "doesnotexist", + Optional: &optional, + }, + }, + }, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + true, "", }, { @@ -116,7 +268,30 @@ func TestEnvVarValue(t *testing.T) { }, }, }, - configMapList, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + false, + "", + }, + { + "OptionalConfigMapDoesNotExist", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + Key: "myvar", + Optional: &optional, + }, + }, + }, + CtrSpecGenOptions{ + ConfigMaps: configMapList, + }, + true, "", }, { @@ -132,7 +307,127 @@ func TestEnvVarValue(t *testing.T) { }, }, }, - []v1.ConfigMap{}, + CtrSpecGenOptions{ + ConfigMaps: []v1.ConfigMap{}, + }, + false, + "", + }, + { + "OptionalEmptyConfigMapList", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ConfigMapKeyRef: &v1.ConfigMapKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "myvar", + Optional: &optional, + }, + }, + }, + CtrSpecGenOptions{ + ConfigMaps: []v1.ConfigMap{}, + }, + true, + "", + }, + { + "SecretExists", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "myvar", + }, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + true, + "foo", + }, + { + "ContainerKeyDoesNotExistInSecret", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "doesnotexist", + }, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + false, + "", + }, + { + "OptionalContainerKeyDoesNotExistInSecret", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "foo", + }, + Key: "doesnotexist", + Optional: &optional, + }, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + true, + "", + }, + { + "SecretDoesNotExist", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + Key: "myvar", + }, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + false, + "", + }, + { + "OptionalSecretDoesNotExist", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "doesnotexist", + }, + Key: "myvar", + Optional: &optional, + }, + }, + }, + CtrSpecGenOptions{ + SecretsManager: secretsManager, + }, + true, "", }, } @@ -140,7 +435,8 @@ func TestEnvVarValue(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - result := envVarValue(test.envVar, test.configMapList) + result, err := envVarValue(test.envVar, &test.options) + assert.Equal(t, err == nil, test.succeed) assert.Equal(t, test.expected, result) }) } @@ -170,3 +466,30 @@ var configMapList = []v1.ConfigMap{ }, }, } + +var optional = true + +var k8sSecrets = []v1.Secret{ + { + TypeMeta: v12.TypeMeta{ + Kind: "Secret", + }, + ObjectMeta: v12.ObjectMeta{ + Name: "bar", + }, + Data: map[string][]byte{ + "myvar": []byte("bar"), + }, + }, + { + TypeMeta: v12.TypeMeta{ + Kind: "Secret", + }, + ObjectMeta: v12.ObjectMeta{ + Name: "foo", + }, + Data: map[string][]byte{ + "myvar": []byte("foo"), + }, + }, +} diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 732579bf0..c10dc5ef5 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -5,7 +5,7 @@ import ( "syscall" "github.com/containers/image/v5/manifest" - "github.com/containers/storage" + "github.com/containers/storage/types" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -302,7 +302,7 @@ type ContainerSecurityConfig struct { // IDMappings are UID and GID mappings that will be used by user // namespaces. // Required if UserNS is private. - IDMappings *storage.IDMappingOptions `json:"idmappings,omitempty"` + IDMappings *types.IDMappingOptions `json:"idmappings,omitempty"` // ReadOnlyFilesystem indicates that everything will be mounted // as read-only ReadOnlyFilesystem bool `json:"read_only_filesystem,omitempty"` diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index 19d468403..eafd45528 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -39,20 +39,46 @@ After=network-online.target RequiresMountsFor={{{{.GraphRoot}}}} {{{{.RunRoot}}}} ` -// filterPodFlags removes --pod and --pod-id-file from the specified command. -func filterPodFlags(command []string) []string { +// filterPodFlags removes --pod, --pod-id-file and --infra-conmon-pidfile from the specified command. +// argCount is the number of last arguments which should not be filtered, e.g. the container entrypoint. +func filterPodFlags(command []string, argCount int) []string { processed := []string{} - for i := 0; i < len(command); i++ { + for i := 0; i < len(command)-argCount; i++ { s := command[i] - if s == "--pod" || s == "--pod-id-file" { + if s == "--pod" || s == "--pod-id-file" || s == "--infra-conmon-pidfile" { i++ continue } - if strings.HasPrefix(s, "--pod=") || strings.HasPrefix(s, "--pod-id-file=") { + if strings.HasPrefix(s, "--pod=") || + strings.HasPrefix(s, "--pod-id-file=") || + strings.HasPrefix(s, "--infra-conmon-pidfile=") { continue } processed = append(processed, s) } + processed = append(processed, command[len(command)-argCount:]...) + return processed +} + +// filterCommonContainerFlags removes --conmon-pidfile, --cidfile and --cgroups from the specified command. +// argCount is the number of last arguments which should not be filtered, e.g. the container entrypoint. +func filterCommonContainerFlags(command []string, argCount int) []string { + processed := []string{} + for i := 0; i < len(command)-argCount; i++ { + s := command[i] + + switch { + case s == "--conmon-pidfile", s == "--cidfile", s == "--cgroups": + i++ + continue + case strings.HasPrefix(s, "--conmon-pidfile="), + strings.HasPrefix(s, "--cidfile="), + strings.HasPrefix(s, "--cgroups="): + continue + } + processed = append(processed, s) + } + processed = append(processed, command[len(command)-argCount:]...) return processed } diff --git a/pkg/systemd/generate/common_test.go b/pkg/systemd/generate/common_test.go index 3787e461e..30e758127 100644 --- a/pkg/systemd/generate/common_test.go +++ b/pkg/systemd/generate/common_test.go @@ -1,7 +1,6 @@ package generate import ( - "strings" "testing" "github.com/stretchr/testify/assert" @@ -9,22 +8,144 @@ import ( func TestFilterPodFlags(t *testing.T) { tests := []struct { - input []string + input []string + output []string + argCount int }{ - {[]string{"podman", "pod", "create"}}, - {[]string{"podman", "pod", "create", "--name", "foo"}}, - {[]string{"podman", "pod", "create", "--pod-id-file", "foo"}}, - {[]string{"podman", "pod", "create", "--pod-id-file=foo"}}, - {[]string{"podman", "run", "--pod", "foo"}}, - {[]string{"podman", "run", "--pod=foo"}}, + { + []string{"podman", "pod", "create"}, + []string{"podman", "pod", "create"}, + 0, + }, + { + []string{"podman", "pod", "create", "--name", "foo"}, + []string{"podman", "pod", "create", "--name", "foo"}, + 0, + }, + { + []string{"podman", "pod", "create", "--pod-id-file", "foo"}, + []string{"podman", "pod", "create"}, + 0, + }, + { + []string{"podman", "pod", "create", "--pod-id-file=foo"}, + []string{"podman", "pod", "create"}, + 0, + }, + { + []string{"podman", "pod", "create", "--pod-id-file", "foo", "--infra-conmon-pidfile", "foo"}, + []string{"podman", "pod", "create"}, + 0, + }, + { + []string{"podman", "pod", "create", "--pod-id-file", "foo", "--infra-conmon-pidfile=foo"}, + []string{"podman", "pod", "create"}, + 0, + }, + { + []string{"podman", "run", "--pod", "foo"}, + []string{"podman", "run"}, + 0, + }, + { + []string{"podman", "run", "--pod=foo"}, + []string{"podman", "run"}, + 0, + }, + { + []string{"podman", "run", "--pod=foo", "fedora", "podman", "run", "--pod=test", "alpine"}, + []string{"podman", "run", "fedora", "podman", "run", "--pod=test", "alpine"}, + 5, + }, + { + []string{"podman", "run", "--pod", "foo", "fedora", "podman", "run", "--pod", "test", "alpine"}, + []string{"podman", "run", "fedora", "podman", "run", "--pod", "test", "alpine"}, + 6, + }, + { + []string{"podman", "run", "--pod-id-file=foo", "fedora", "podman", "run", "--pod-id-file=test", "alpine"}, + []string{"podman", "run", "fedora", "podman", "run", "--pod-id-file=test", "alpine"}, + 5, + }, + { + []string{"podman", "run", "--pod-id-file", "foo", "fedora", "podman", "run", "--pod-id-file", "test", "alpine"}, + []string{"podman", "run", "fedora", "podman", "run", "--pod-id-file", "test", "alpine"}, + 6, + }, + } + + for _, test := range tests { + processed := filterPodFlags(test.input, test.argCount) + assert.Equal(t, test.output, processed) + } +} + +func TestFilterCommonContainerFlags(t *testing.T) { + tests := []struct { + input []string + output []string + argCount int + }{ + { + []string{"podman", "run", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--conmon-pidfile", "foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--conmon-pidfile=foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--cidfile", "foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--cidfile=foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--cgroups", "foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--cgroups=foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--cgroups", "foo", "--conmon-pidfile", "foo", "--cidfile", "foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "alpine"}, + []string{"podman", "run", "alpine"}, + 1, + }, + { + []string{"podman", "run", "--cgroups", "foo", "--conmon-pidfile", "foo", "--cidfile", "foo", "alpine", "--cgroups", "foo", "--conmon-pidfile", "foo", "--cidfile", "foo"}, + []string{"podman", "run", "alpine", "--cgroups", "foo", "--conmon-pidfile", "foo", "--cidfile", "foo"}, + 7, + }, + { + []string{"podman", "run", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "alpine", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo"}, + []string{"podman", "run", "alpine", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo"}, + 4, + }, } for _, test := range tests { - processed := filterPodFlags(test.input) - for _, s := range processed { - assert.False(t, strings.HasPrefix(s, "--pod-id-file")) - assert.False(t, strings.HasPrefix(s, "--pod")) - } + processed := filterCommonContainerFlags(test.input, test.argCount) + assert.Equal(t, test.output, processed) } } diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index bc13a6116..e06655a8d 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -238,13 +238,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst "--cidfile", "{{{{.ContainerIDFile}}}}", "--cgroups=no-conmon", ) - // If the container is in a pod, make sure that the - // --pod-id-file is set correctly. - if info.Pod != nil { - podFlags := []string{"--pod-id-file", "{{{{.Pod.PodIDFile}}}}"} - startCommand = append(startCommand, podFlags...) - info.CreateCommand = filterPodFlags(info.CreateCommand) - } + remainingCmd := info.CreateCommand[index:] // Presence check for certain flags/options. fs := pflag.NewFlagSet("args", pflag.ContinueOnError) @@ -254,7 +248,16 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst fs.BoolP("detach", "d", false, "") fs.String("name", "", "") fs.Bool("replace", false, "") - fs.Parse(info.CreateCommand[index:]) + fs.Parse(remainingCmd) + + remainingCmd = filterCommonContainerFlags(remainingCmd, fs.NArg()) + // If the container is in a pod, make sure that the + // --pod-id-file is set correctly. + if info.Pod != nil { + podFlags := []string{"--pod-id-file", "{{{{.Pod.PodIDFile}}}}"} + startCommand = append(startCommand, podFlags...) + remainingCmd = filterPodFlags(remainingCmd, fs.NArg()) + } hasDetachParam, err := fs.GetBool("detach") if err != nil { @@ -266,8 +269,6 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst return "", err } - remainingCmd := info.CreateCommand[index:] - if !hasDetachParam { // Enforce detaching // diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 1359c1a37..899ba6bfa 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -395,6 +395,56 @@ Type=forking [Install] WantedBy=multi-user.target default.target ` + + goodNewWithIDFiles := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target +RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d awesome-image:latest podman run --cgroups=foo --conmon-pidfile=foo --cidfile=foo alpine +ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 10 +ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +PIDFile=%t/jadda-jadda.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` + + goodNewWithPodIDFiles := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network.target +After=network-online.target +RequiresMountsFor=/var/lib/containers/storage /var/run/containers/storage + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=always +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id +ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --pod-id-file %t/pod-foobar.pod-id-file -d awesome-image:latest podman run --cgroups=foo --conmon-pidfile=foo --cidfile=foo --pod-id-file /tmp/pod-foobar.pod-id-file alpine +ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 10 +ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id +PIDFile=%t/jadda-jadda.pid +Type=forking + +[Install] +WantedBy=multi-user.target default.target +` tests := []struct { name string info containerInfo @@ -782,6 +832,47 @@ WantedBy=multi-user.target default.target false, false, }, + {"good with ID files", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + CreateCommand: []string{"I'll get stripped", "create", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "awesome-image:latest", "podman", "run", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "alpine"}, + EnvVariable: define.EnvVariable, + }, + goodNewWithIDFiles, + true, + false, + false, + }, + {"good with pod ID files", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + CreateCommand: []string{"I'll get stripped", "create", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "--pod", "test", "awesome-image:latest", "podman", "run", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "--pod-id-file", "/tmp/pod-foobar.pod-id-file", "alpine"}, + EnvVariable: define.EnvVariable, + Pod: &podInfo{ + PodIDFile: "%t/pod-foobar.pod-id-file", + }, + }, + goodNewWithPodIDFiles, + true, + false, + false, + }, } for _, tt := range tests { test := tt diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index a76979ecf..1b92649e8 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -279,16 +279,16 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) } podRootArgs = info.CreateCommand[1 : podCreateIndex-1] info.RootFlags = strings.Join(escapeSystemdArguments(podRootArgs), " ") - podCreateArgs = filterPodFlags(info.CreateCommand[podCreateIndex+1:]) + podCreateArgs = filterPodFlags(info.CreateCommand[podCreateIndex+1:], 0) } // We're hard-coding the first five arguments and append the // CreateCommand with a stripped command and subcommand. startCommand := []string{info.Executable} startCommand = append(startCommand, podRootArgs...) startCommand = append(startCommand, - []string{"pod", "create", - "--infra-conmon-pidfile", "{{{{.PIDFile}}}}", - "--pod-id-file", "{{{{.PodIDFile}}}}"}...) + "pod", "create", + "--infra-conmon-pidfile", "{{{{.PIDFile}}}}", + "--pod-id-file", "{{{{.PodIDFile}}}}") // Presence check for certain flags/options. fs := pflag.NewFlagSet("args", pflag.ContinueOnError) diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index 559f7365f..0e4d92c50 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -320,6 +320,25 @@ WantedBy=multi-user.target default.target false, false, }, + {"pod --new with ID files", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + RestartPolicy: "on-failure", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + RequiredServices: []string{"container-1", "container-2"}, + CreateCommand: []string{"podman", "pod", "create", "--infra-conmon-pidfile", "/tmp/pod-123abc.pid", "--pod-id-file", "/tmp/pod-123abc.pod-id", "--name", "foo", "bar=arg with space"}, + }, + podGoodNamedNew, + true, + false, + false, + }, } for _, tt := range tests { diff --git a/pkg/util/utils.go b/pkg/util/utils.go index a4c8f3a64..bbaf72981 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -20,8 +20,8 @@ import ( "github.com/containers/podman/v3/pkg/namespaces" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/signal" - "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" + stypes "github.com/containers/storage/types" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -344,8 +344,8 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) { } // GetKeepIDMapping returns the mappings and the user to use when keep-id is used -func GetKeepIDMapping() (*storage.IDMappingOptions, int, int, error) { - options := storage.IDMappingOptions{ +func GetKeepIDMapping() (*stypes.IDMappingOptions, int, int, error) { + options := stypes.IDMappingOptions{ HostUIDMapping: true, HostGIDMapping: true, } @@ -395,8 +395,8 @@ func GetKeepIDMapping() (*storage.IDMappingOptions, int, int, error) { } // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping -func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) { - options := storage.IDMappingOptions{ +func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) { + options := stypes.IDMappingOptions{ HostUIDMapping: true, HostGIDMapping: true, } @@ -479,7 +479,7 @@ type tomlConfig struct { } `toml:"storage"` } -func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { +func getTomlStorage(storeOptions *stypes.StoreOptions) *tomlConfig { config := new(tomlConfig) config.Storage.Driver = storeOptions.GraphDriverName @@ -496,7 +496,7 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { } // WriteStorageConfigFile writes the configuration to a file -func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf string) error { +func WriteStorageConfigFile(storageOpts *stypes.StoreOptions, storageConf string) error { if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { return err } |