mirror of https://github.com/containers/podman.git
Merge pull request #25225 from mheon/bump_540_rc3
[CI:ALL] Bump to v5.4.0-RC3
This commit is contained in:
commit
582d7185df
|
@ -6,7 +6,7 @@ env:
|
|||
#### Global variables used for all tasks
|
||||
####
|
||||
# Name of the ultimate destination branch for this CI run, PR or post-merge.
|
||||
DEST_BRANCH: "main"
|
||||
DEST_BRANCH: "v5.4"
|
||||
# Sane (default) value for GOPROXY and GOSUMDB.
|
||||
GOPROXY: "https://proxy.golang.org,direct"
|
||||
GOSUMDB: "sum.golang.org"
|
||||
|
|
1
Makefile
1
Makefile
|
@ -119,6 +119,7 @@ GOFLAGS ?= -trimpath
|
|||
LDFLAGS_PODMAN ?= \
|
||||
$(if $(GIT_COMMIT),-X $(LIBPOD)/define.gitCommit=$(GIT_COMMIT),) \
|
||||
$(if $(BUILD_INFO),-X $(LIBPOD)/define.buildInfo=$(BUILD_INFO),) \
|
||||
$(if $(BUILD_ORIGIN),-X $(LIBPOD)/define.buildOrigin=$(BUILD_ORIGIN),) \
|
||||
-X $(LIBPOD)/config._installPrefix=$(PREFIX) \
|
||||
-X $(LIBPOD)/config._etcDir=$(ETCDIR) \
|
||||
-X $(PROJECT)/v5/pkg/systemd/quadlet._binDir=$(BINDIR) \
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
- The `--mount type=volume` option for the `podman run`, `podman create`, and `podman volume create` commands now supports a new option, `subpath=`, to make only a subset of the volume visible in the container ([#20661](https://github.com/containers/podman/issues/20661)).
|
||||
- The `--userns=keep-id` option for the `podman run`, `podman create`, and `podman pod create` commands now supports a new option, `--userns=keep-id:size=`, to configure the size of the user namespace ([#24387](https://github.com/containers/podman/issues/24837)).
|
||||
- The `podman kube play` command now supports Container Device Interface (CDI) devices ([#17833](https://github.com/containers/podman/issues/17833)).
|
||||
- The `podman machine init` command now supports a new option, `--playbook`, to run an Ansible playbook in the created VM on first boot for initial configuration.
|
||||
- Quadlet `.pod` files now support a new field, `ShmSize`, to specify the size of the pod's shared SHM ([#22915](https://github.com/containers/podman/issues/22915)).
|
||||
- The `podman run`, `podman create`, and `podman pod create` commands now support a new option, `--hosts-file`, to define the base file used for `/etc/hosts` in the container.
|
||||
- The `podman run`, `podman create`, and `podman pod create` commands now support a new option, `--no-hostname`, which disables the creation of `/etc/hostname` in the container ([#25002](https://github.com/containers/podman/issues/25002)).
|
||||
|
@ -23,6 +24,7 @@
|
|||
### Changes
|
||||
- Podman now passes container hostnames to Netavark, which will use them for any DHCP requests for the container.
|
||||
- Partial pulls of `zstd:chunked` images now only happen for images that have a `RootFS.DiffID` entry in the image's OCI config JSON, and require the layer contents to match. This resolves issues with image ID ambiguity when partial pulls were enabled.
|
||||
- Packagers can now set the `BUILD_ORIGIN` environment variable when building podman from the `Makefile`. This provides information on who built the Podman binary, and is displayed in `podman version` and `podman info`. This will help upstream bug reports, allowing maintainers to trace how and where the binary was built and installed from.
|
||||
|
||||
### Bugfixes
|
||||
- Fixed a bug where `podman machine` VMs on WSL could fail to start when using usermode networking could fail to start due to a port conflict ([#20327](https://github.com/containers/podman/issues/20327)).
|
||||
|
@ -46,13 +48,20 @@
|
|||
- Fixed a bug where the output of `podman inspect` on containers did not include the ID of the network the container was joined to, improving Docker compatibility ([#24910](https://github.com/containers/podman/issues/24910)).
|
||||
- Fixed a bug where containers created with the remote API incorrectly included a create command ([#25026](https://github.com/containers/podman/issues/25026)).
|
||||
- Fixed a bug where it was possible to specify the `libkrun` backend for VMs on Intel Macs (`libkrun` only supports Arm systems).
|
||||
- Fixed a bug where `libkrun` and `applehv` VMs from `podman machine` could be started at the same time on Macs ([#25112](https://github.com/containers/podman/issues/25112)).
|
||||
- Fixed a bug where `podman exec` commands could not detach from the exec session using the detach keys ([#24895](https://github.com/containers/podman/issues/24895)).
|
||||
|
||||
### API
|
||||
- The Compat and Libpod Build APIs for Images now support a new query parameter, `nohosts`, which (when set to true) does not create `/etc/hosts` in the image when building.
|
||||
- Fixed a bug where the Compat Create API for Containers did not honor CDI devices, preventing (among other things) the use of GPUs with `docker compose` ([#19338](https://github.com/containers/podman/issues/19338)).
|
||||
|
||||
### Misc
|
||||
- The Docker alias script has been fixed to better handle variable substitution.
|
||||
- Fixed a bug where `podman-restart.service` functioned incorrectly when no containers were present.
|
||||
- Updated Buildah to v1.39.0
|
||||
- Updated the containers/common library to v0.62.0
|
||||
- Updated the containers/storage library to v1.57.1
|
||||
- Updated the containers/image library to v5.34.0
|
||||
|
||||
## 5.3.2
|
||||
### Security
|
||||
|
|
|
@ -3,15 +3,17 @@ package artifact
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v5/cmd/podman/common"
|
||||
"github.com/containers/podman/v5/cmd/podman/registry"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
"github.com/containers/podman/v5/pkg/domain/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
addCmd = &cobra.Command{
|
||||
Use: "add ARTIFACT PATH [...PATH]",
|
||||
Use: "add [options] ARTIFACT PATH [...PATH]",
|
||||
Short: "Add an OCI artifact to the local store",
|
||||
Long: "Add an OCI artifact to the local store from the local filesystem",
|
||||
RunE: add,
|
||||
|
@ -22,15 +24,41 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
type artifactAddOptions struct {
|
||||
ArtifactType string
|
||||
Annotations []string
|
||||
}
|
||||
|
||||
var (
|
||||
addOpts artifactAddOptions
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Command: addCmd,
|
||||
Parent: artifactCmd,
|
||||
})
|
||||
flags := addCmd.Flags()
|
||||
|
||||
annotationFlagName := "annotation"
|
||||
flags.StringArrayVar(&addOpts.Annotations, annotationFlagName, nil, "set an `annotation` for the specified artifact")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone)
|
||||
|
||||
addTypeFlagName := "type"
|
||||
flags.StringVar(&addOpts.ArtifactType, addTypeFlagName, "", "Use type to describe an artifact")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(addTypeFlagName, completion.AutocompleteNone)
|
||||
}
|
||||
|
||||
func add(cmd *cobra.Command, args []string) error {
|
||||
report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], entities.ArtifactAddoptions{})
|
||||
opts := new(entities.ArtifactAddOptions)
|
||||
|
||||
annots, err := utils.ParseAnnotations(addOpts.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Annotations = annots
|
||||
opts.ArtifactType = addOpts.ArtifactType
|
||||
report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -31,7 +31,9 @@ var (
|
|||
)
|
||||
|
||||
type listFlagType struct {
|
||||
format string
|
||||
format string
|
||||
noHeading bool
|
||||
noTrunc bool
|
||||
}
|
||||
|
||||
type artifactListOutput struct {
|
||||
|
@ -54,6 +56,8 @@ func init() {
|
|||
formatFlagName := "format"
|
||||
flags.StringVar(&listFlag.format, formatFlagName, defaultArtifactListOutputFormat, "Format volume output using JSON or a Go template")
|
||||
_ = listCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&artifactListOutput{}))
|
||||
flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print column headings")
|
||||
flags.BoolVar(&listFlag.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||
}
|
||||
|
||||
func list(cmd *cobra.Command, _ []string) error {
|
||||
|
@ -95,10 +99,15 @@ func outputTemplate(cmd *cobra.Command, lrs []*entities.ArtifactListReport) erro
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO when we default to shorter ids, i would foresee a switch
|
||||
// like images that will show the full ids.
|
||||
|
||||
artifactHash := artifactDigest.Encoded()[0:12]
|
||||
// If the user does not want truncated hashes
|
||||
if listFlag.noTrunc {
|
||||
artifactHash = artifactDigest.Encoded()
|
||||
}
|
||||
|
||||
artifacts = append(artifacts, artifactListOutput{
|
||||
Digest: artifactDigest.Encoded(),
|
||||
Digest: artifactHash,
|
||||
Repository: named.Name(),
|
||||
Size: units.HumanSize(float64(lr.Artifact.TotalSizeBytes())),
|
||||
Tag: tag,
|
||||
|
@ -125,7 +134,7 @@ func outputTemplate(cmd *cobra.Command, lrs []*entities.ArtifactListReport) erro
|
|||
return err
|
||||
}
|
||||
|
||||
if rpt.RenderHeaders {
|
||||
if rpt.RenderHeaders && !listFlag.noHeading {
|
||||
if err := rpt.Execute(headers); err != nil {
|
||||
return fmt.Errorf("failed to write report column headers: %w", err)
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@ package main
|
|||
import "github.com/containers/podman/v5/libpod/define"
|
||||
|
||||
type clientInfo struct {
|
||||
OSArch string `json:"OS"`
|
||||
Provider string `json:"provider"`
|
||||
Version string `json:"version"`
|
||||
OSArch string `json:"OS"`
|
||||
Provider string `json:"provider"`
|
||||
Version string `json:"version"`
|
||||
BuildOrigin string `json:"buildOrigin,omitempty" yaml:",omitempty"`
|
||||
}
|
||||
|
||||
func getClientInfo() (*clientInfo, error) {
|
||||
|
@ -18,8 +19,9 @@ func getClientInfo() (*clientInfo, error) {
|
|||
return nil, err
|
||||
}
|
||||
return &clientInfo{
|
||||
OSArch: vinfo.OsArch,
|
||||
Provider: p,
|
||||
Version: vinfo.Version,
|
||||
OSArch: vinfo.OsArch,
|
||||
Provider: p,
|
||||
Version: vinfo.Version,
|
||||
BuildOrigin: vinfo.BuildOrigin,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package common
|
|||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
@ -1646,8 +1647,8 @@ func AutocompleteContainersConfModules(cmd *cobra.Command, args []string, toComp
|
|||
for _, d := range dirs {
|
||||
cleanedD := filepath.Clean(d)
|
||||
moduleD := cleanedD + string(os.PathSeparator)
|
||||
_ = filepath.Walk(d,
|
||||
func(path string, f os.FileInfo, err error) error {
|
||||
_ = filepath.WalkDir(d,
|
||||
func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1657,7 +1658,7 @@ func AutocompleteContainersConfModules(cmd *cobra.Command, args []string, toComp
|
|||
return nil
|
||||
}
|
||||
|
||||
if filepath.Clean(path) == cleanedD || f.IsDir() {
|
||||
if filepath.Clean(path) == cleanedD || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,10 @@ func init() {
|
|||
)
|
||||
_ = initCmd.RegisterFlagCompletionFunc(cpusFlagName, completion.AutocompleteNone)
|
||||
|
||||
runPlaybookFlagName := "playbook"
|
||||
flags.StringVar(&initOpts.PlaybookPath, runPlaybookFlagName, "", "Run an Ansible playbook after first boot")
|
||||
_ = initCmd.RegisterFlagCompletionFunc(runPlaybookFlagName, completion.AutocompleteDefault)
|
||||
|
||||
diskSizeFlagName := "disk-size"
|
||||
flags.Uint64Var(
|
||||
&initOpts.DiskSize,
|
||||
|
|
|
@ -45,6 +45,11 @@ var (
|
|||
debug bool
|
||||
)
|
||||
|
||||
type infoReport struct {
|
||||
define.Info
|
||||
Client *define.Version `json:",omitempty" yaml:",omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Command: infoCommand,
|
||||
|
@ -74,12 +79,21 @@ func info(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remote := registry.IsRemote()
|
||||
info.Host.ServiceIsRemote = remote
|
||||
|
||||
info.Host.ServiceIsRemote = registry.IsRemote()
|
||||
infoReport := infoReport{
|
||||
Info: *info,
|
||||
}
|
||||
|
||||
if remote {
|
||||
clientVers, _ := define.GetVersion()
|
||||
infoReport.Client = &clientVers
|
||||
}
|
||||
|
||||
switch {
|
||||
case report.IsJSON(inFormat):
|
||||
b, err := json.MarshalIndent(info, "", " ")
|
||||
b, err := json.MarshalIndent(infoReport, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -94,9 +108,9 @@ func info(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rpt.Execute(info)
|
||||
return rpt.Execute(infoReport)
|
||||
default:
|
||||
b, err := yaml.Marshal(info)
|
||||
b, err := yaml.Marshal(infoReport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ API Version:\t{{.APIVersion}}
|
|||
Go Version:\t{{.GoVersion}}
|
||||
{{if .GitCommit -}}Git Commit:\t{{.GitCommit}}\n{{end -}}
|
||||
Built:\t{{.BuiltTime}}
|
||||
{{if .BuildOrigin -}}Build Origin:\t{{.BuildOrigin}}\n{{end -}}
|
||||
OS/Arch:\t{{.OsArch}}
|
||||
{{- end}}
|
||||
|
||||
|
@ -108,6 +109,7 @@ API Version:\t{{.APIVersion}}
|
|||
Go Version:\t{{.GoVersion}}
|
||||
{{if .GitCommit -}}Git Commit:\t{{.GitCommit}}\n{{end -}}
|
||||
Built:\t{{.BuiltTime}}
|
||||
{{if .BuildOrigin -}}Build Origin:\t{{.BuildOrigin}}\n{{end -}}
|
||||
OS/Arch:\t{{.OsArch}}
|
||||
{{- end}}{{- end}}
|
||||
`
|
||||
|
|
|
@ -9,6 +9,7 @@ PRODUCTSIGN_IDENTITY=${PRODUCTSIGN_IDENTITY:-mock}
|
|||
NO_CODESIGN=${NO_CODESIGN:-0}
|
||||
HELPER_BINARIES_DIR="/opt/podman/bin"
|
||||
MACHINE_POLICY_JSON_DIR="/opt/podman/config"
|
||||
BUILD_ORIGIN="pkginstaller"
|
||||
|
||||
tmpBin="contrib/pkginstaller/tmp-bin"
|
||||
|
||||
|
@ -47,7 +48,7 @@ function build_podman() {
|
|||
}
|
||||
|
||||
function build_podman_arch(){
|
||||
make -B GOARCH="$1" podman-remote HELPER_BINARIES_DIR="${HELPER_BINARIES_DIR}"
|
||||
make -B GOARCH="$1" podman-remote HELPER_BINARIES_DIR="${HELPER_BINARIES_DIR}" BUILD_ORIGIN="${BUILD_ORIGIN}"
|
||||
make -B GOARCH="$1" podman-mac-helper
|
||||
mkdir -p "${tmpBin}"
|
||||
cp bin/darwin/podman "${tmpBin}/podman-$1"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
podman-artifact-add.1.md
|
||||
podman-artifact-ls.1.md
|
||||
podman-artifact-pull.1.md
|
||||
podman-artifact-push.1.md
|
||||
podman-attach.1.md
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
####> This option file is used in:
|
||||
####> podman manifest add, manifest annotate
|
||||
####> podman artifact add, manifest add, manifest annotate
|
||||
####> If file is edited, make sure the changes
|
||||
####> are applicable to all of those.
|
||||
#### **--annotation**=*annotation=value*
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
####> This option file is used in:
|
||||
####> podman artifact ls, images
|
||||
####> If file is edited, make sure the changes
|
||||
####> are applicable to all of those.
|
||||
#### **--no-trunc**
|
||||
|
||||
Do not truncate the output (default *false*).
|
|
@ -1,5 +1,5 @@
|
|||
####> This option file is used in:
|
||||
####> podman image trust, images, machine list, network ls, pod ps, secret ls, volume ls
|
||||
####> podman artifact ls, image trust, images, machine list, network ls, pod ps, secret ls, volume ls
|
||||
####> If file is edited, make sure the changes
|
||||
####> are applicable to all of those.
|
||||
#### **--noheading**, **-n**
|
||||
|
|
|
@ -19,10 +19,15 @@ added.
|
|||
|
||||
## OPTIONS
|
||||
|
||||
@@option annotation.manifest
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
||||
#### **--type**
|
||||
|
||||
Set a type for the artifact being added.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
@ -39,6 +44,10 @@ $ podman artifact add quay.io/myartifact/myml:latest /tmp/foobar1.ml /tmp/foobar
|
|||
1487acae11b5a30948c50762882036b41ac91a7b9514be8012d98015c95ddb78
|
||||
```
|
||||
|
||||
Set an annotation for an artifact
|
||||
```
|
||||
$ podman artifact add --annotation date=2025-01-30 quay.io/myartifact/myml:latest /tmp/foobar1.ml
|
||||
```
|
||||
|
||||
|
||||
## SEE ALSO
|
|
@ -28,24 +28,40 @@ Print results with a Go template.
|
|||
| .Size | Size artifact in human readable units |
|
||||
| .Tag | Tag of the artifact name |
|
||||
|
||||
@@option no-trunc
|
||||
|
||||
@@option noheading
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
List artifacts in the local store
|
||||
```
|
||||
$ podman artifact ls
|
||||
REPOSITORY TAG DIGEST SIZE
|
||||
quay.io/artifact/foobar1 latest ab609fad386df1433f461b0643d9cf575560baf633809dcc9c190da6cc3a3c29 2.097GB
|
||||
quay.io/artifact/foobar2 special cd734b558ceb8ccc0281ca76530e1dea1eb479407d3163f75fb601bffb6f73d0 12.58MB
|
||||
|
||||
|
||||
REPOSITORY TAG DIGEST SIZE
|
||||
quay.io/artifact/foobar1 latest ab609fad386d 2.097GB
|
||||
quay.io/artifact/foobar2 special cd734b558ceb 12.58MB
|
||||
```
|
||||
|
||||
List artifacts in the local store without truncating the digest
|
||||
```
|
||||
$ podman artifact ls --no-trunc
|
||||
REPOSITORY TAG DIGEST SIZE
|
||||
quay.io/artifact/foobar1 latest ab609fad386df1433f461b0643d9cf575560baf633809dcc9c190da6cc3a3c29 2.097GB
|
||||
quay.io/artifact/foobar2 special cd734b558ceb8ccc0281ca76530e1dea1eb479407d3163f75fb601bffb6f73d0 12.58MB
|
||||
```
|
||||
|
||||
List artifacts in the local store without the title header
|
||||
```
|
||||
$ podman artifact ls --noheading
|
||||
quay.io/artifact/foobar1 latest ab609fad386d 2.097GB
|
||||
quay.io/artifact/foobar2 special cd734b558ceb 12.58MB
|
||||
```
|
||||
|
||||
List artifact digests and size using a --format
|
||||
```
|
||||
$ podman artifact ls --format "{{.Digest}} {{.Size}}"
|
||||
ab609fad386df1433f461b0643d9cf575560baf633809dcc9c190da6cc3a3c29 2.097GB
|
||||
cd734b558ceb8ccc0281ca76530e1dea1eb479407d3163f75fb601bffb6f73d0 12.58MB
|
||||
ab609fad386d 2.097GB
|
||||
cd734b558ceb 12.58MB
|
||||
```
|
||||
|
||||
|
|
@ -106,9 +106,7 @@ Valid placeholders for the Go template are listed below:
|
|||
|
||||
Display the history of image names. If an image gets re-tagged or untagged, then the image name history gets prepended (latest image first). This is especially useful when undoing a tag operation or an image does not contain any name because it has been untagged.
|
||||
|
||||
#### **--no-trunc**
|
||||
|
||||
Do not truncate the output (default *false*).
|
||||
@@option no-trunc
|
||||
|
||||
@@option noheading
|
||||
|
||||
|
|
|
@ -80,6 +80,8 @@ is copied into the user's CONF_DIR and renamed. Additionally, no SSH keys are g
|
|||
Fully qualified registry, path, or URL to a VM image.
|
||||
Registry target must be in the form of `docker://registry/repo/image:version`.
|
||||
|
||||
Note: Only images provided by podman will be supported.
|
||||
|
||||
#### **--memory**, **-m**=*number*
|
||||
|
||||
Memory (in MiB). Note: 1024MiB = 1GiB.
|
||||
|
@ -88,6 +90,13 @@ Memory (in MiB). Note: 1024MiB = 1GiB.
|
|||
|
||||
Start the virtual machine immediately after it has been initialized.
|
||||
|
||||
#### **--playbook**
|
||||
|
||||
Add the provided Ansible playbook to the machine and execute it after the first boot.
|
||||
|
||||
Note: The playbook will be executed with the same privileges given to the user in the virtual machine. The playbook provided cannot include other files from the host system, as they will not be copied.
|
||||
Use of the `--playbook` flag will require the image to include Ansible. The default image provided will have Ansible included.
|
||||
|
||||
#### **--rootful**
|
||||
|
||||
Whether this machine prefers rootful (`true`) or rootless (`false`)
|
||||
|
|
|
@ -16,18 +16,22 @@ var (
|
|||
// BuildInfo is the time at which the binary was built
|
||||
// It will be populated by the Makefile.
|
||||
buildInfo string
|
||||
// BuildOrigin is the packager of the binary.
|
||||
// It will be populated at build-time.
|
||||
buildOrigin string
|
||||
)
|
||||
|
||||
// Version is an output struct for API
|
||||
type Version struct {
|
||||
APIVersion string
|
||||
Version string
|
||||
GoVersion string
|
||||
GitCommit string
|
||||
BuiltTime string
|
||||
Built int64
|
||||
OsArch string
|
||||
Os string
|
||||
APIVersion string
|
||||
Version string
|
||||
GoVersion string
|
||||
GitCommit string
|
||||
BuiltTime string
|
||||
Built int64
|
||||
BuildOrigin string `json:",omitempty" yaml:",omitempty"`
|
||||
OsArch string
|
||||
Os string
|
||||
}
|
||||
|
||||
// GetVersion returns a VersionOutput struct for API and podman
|
||||
|
@ -43,13 +47,14 @@ func GetVersion() (Version, error) {
|
|||
}
|
||||
}
|
||||
return Version{
|
||||
APIVersion: version.APIVersion[version.Libpod][version.CurrentAPI].String(),
|
||||
Version: version.Version.String(),
|
||||
GoVersion: runtime.Version(),
|
||||
GitCommit: gitCommit,
|
||||
BuiltTime: time.Unix(buildTime, 0).Format(time.ANSIC),
|
||||
Built: buildTime,
|
||||
OsArch: runtime.GOOS + "/" + runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
APIVersion: version.APIVersion[version.Libpod][version.CurrentAPI].String(),
|
||||
Version: version.Version.String(),
|
||||
GoVersion: runtime.Version(),
|
||||
GitCommit: gitCommit,
|
||||
BuiltTime: time.Unix(buildTime, 0).Format(time.ANSIC),
|
||||
Built: buildTime,
|
||||
BuildOrigin: buildOrigin,
|
||||
OsArch: runtime.GOOS + "/" + runtime.GOARCH,
|
||||
Os: runtime.GOOS,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -163,6 +163,11 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C
|
|||
for _, dev := range cc.HostConfig.Devices {
|
||||
devices = append(devices, fmt.Sprintf("%s:%s:%s", dev.PathOnHost, dev.PathInContainer, dev.CgroupPermissions))
|
||||
}
|
||||
for _, r := range cc.HostConfig.Resources.DeviceRequests {
|
||||
if r.Driver == "cdi" {
|
||||
devices = append(devices, r.DeviceIDs...)
|
||||
}
|
||||
}
|
||||
|
||||
// iterate blkreaddevicebps
|
||||
readBps := make([]string, 0, len(cc.HostConfig.BlkioDeviceReadBps))
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/containers/podman/v5/pkg/channel"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
"github.com/containers/podman/v5/pkg/domain/infra/abi"
|
||||
domainUtils "github.com/containers/podman/v5/pkg/domain/utils"
|
||||
"github.com/containers/podman/v5/pkg/errorhandling"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/schema"
|
||||
|
@ -520,24 +521,17 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
annotationsFromAnnotationSlice := func(annotation []string) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for _, annotationSpec := range annotation {
|
||||
key, val, hasVal := strings.Cut(annotationSpec, "=")
|
||||
if !hasVal {
|
||||
utils.Error(w, http.StatusBadRequest, fmt.Errorf("no value given for annotation %q", key))
|
||||
return nil
|
||||
}
|
||||
annotations[key] = val
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
if len(body.ManifestAddOptions.Annotation) != 0 {
|
||||
if len(body.ManifestAddOptions.Annotations) != 0 {
|
||||
utils.Error(w, http.StatusBadRequest, fmt.Errorf("can not set both Annotation and Annotations"))
|
||||
return
|
||||
}
|
||||
body.ManifestAddOptions.Annotations = annotationsFromAnnotationSlice(body.ManifestAddOptions.Annotation)
|
||||
annots, err := domainUtils.ParseAnnotations(body.ManifestAddOptions.Annotation)
|
||||
if err != nil {
|
||||
utils.Error(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
body.ManifestAddOptions.Annotations = annots
|
||||
body.ManifestAddOptions.Annotation = nil
|
||||
}
|
||||
if len(body.ManifestAddOptions.IndexAnnotation) != 0 {
|
||||
|
@ -545,7 +539,12 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
|
|||
utils.Error(w, http.StatusBadRequest, fmt.Errorf("can not set both IndexAnnotation and IndexAnnotations"))
|
||||
return
|
||||
}
|
||||
body.ManifestAddOptions.IndexAnnotations = annotationsFromAnnotationSlice(body.ManifestAddOptions.IndexAnnotation)
|
||||
annots, err := domainUtils.ParseAnnotations(body.ManifestAddOptions.IndexAnnotation)
|
||||
if err != nil {
|
||||
utils.Error(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
body.ManifestAddOptions.IndexAnnotations = annots
|
||||
body.ManifestAddOptions.IndexAnnotation = nil
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import (
|
|||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
type ArtifactAddoptions struct {
|
||||
type ArtifactAddOptions struct {
|
||||
Annotations map[string]string
|
||||
ArtifactType string
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
type ImageEngine interface { //nolint:interfacebloat
|
||||
ArtifactAdd(ctx context.Context, name string, paths []string, opts ArtifactAddoptions) (*ArtifactAddReport, error)
|
||||
ArtifactAdd(ctx context.Context, name string, paths []string, opts *ArtifactAddOptions) (*ArtifactAddReport, error)
|
||||
ArtifactInspect(ctx context.Context, name string, opts ArtifactInspectOptions) (*ArtifactInspectReport, error)
|
||||
ArtifactList(ctx context.Context, opts ArtifactListOptions) ([]*ArtifactListReport, error)
|
||||
ArtifactPull(ctx context.Context, name string, opts ArtifactPullOptions) (*ArtifactPullReport, error)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/containers/common/libimage"
|
||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||
"github.com/containers/podman/v5/pkg/libartifact/store"
|
||||
"github.com/containers/podman/v5/pkg/libartifact/types"
|
||||
)
|
||||
|
||||
func getDefaultArtifactStore(ir *ImageEngine) string {
|
||||
|
@ -152,12 +153,18 @@ func (ir *ImageEngine) ArtifactPush(ctx context.Context, name string, opts entit
|
|||
err = artStore.Push(ctx, name, name, copyOpts)
|
||||
return &entities.ArtifactPushReport{}, err
|
||||
}
|
||||
func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []string, opts entities.ArtifactAddoptions) (*entities.ArtifactAddReport, error) {
|
||||
func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []string, opts *entities.ArtifactAddOptions) (*entities.ArtifactAddReport, error) {
|
||||
artStore, err := store.NewArtifactStore(getDefaultArtifactStore(ir), ir.Libpod.SystemContext())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifactDigest, err := artStore.Add(ctx, name, paths, opts.ArtifactType)
|
||||
|
||||
addOptions := types.AddOptions{
|
||||
Annotations: opts.Annotations,
|
||||
ArtifactType: opts.ArtifactType,
|
||||
}
|
||||
|
||||
artifactDigest, err := artStore.Add(ctx, name, paths, &addOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
// TODO For now, no remote support has been added. We need the API to firm up first.
|
||||
|
||||
func ArtifactAdd(ctx context.Context, path, name string, opts entities.ArtifactAddoptions) error {
|
||||
func ArtifactAdd(ctx context.Context, path, name string, opts entities.ArtifactAddOptions) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,6 @@ func (ir *ImageEngine) ArtifactPush(ctx context.Context, name string, opts entit
|
|||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []string, opts entities.ArtifactAddoptions) (*entities.ArtifactAddReport, error) {
|
||||
func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []string, opts *entities.ArtifactAddOptions) (*entities.ArtifactAddReport, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
|
@ -39,3 +40,17 @@ func ToURLValues(f []string) (filters url.Values) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseAnnotations takes a string slice of options, expected to be "key=val" and returns
|
||||
// a string map where the map index is the key and points to the value
|
||||
func ParseAnnotations(options []string) (map[string]string, error) {
|
||||
annotations := make(map[string]string)
|
||||
for _, annotationSpec := range options {
|
||||
key, val, hasVal := strings.Cut(annotationSpec, "=")
|
||||
if !hasVal {
|
||||
return nil, fmt.Errorf("no value given for annotation %q", key)
|
||||
}
|
||||
annotations[key] = val
|
||||
}
|
||||
return annotations, nil
|
||||
}
|
||||
|
|
|
@ -11,17 +11,17 @@ import (
|
|||
)
|
||||
|
||||
type Artifact struct {
|
||||
Manifests []manifest.OCI1
|
||||
Name string
|
||||
// Manifest is the OCI manifest for the artifact with the name.
|
||||
// In a valid artifact the Manifest is guaranteed to not be nil.
|
||||
Manifest *manifest.OCI1
|
||||
Name string
|
||||
}
|
||||
|
||||
// TotalSizeBytes returns the total bytes of the all the artifact layers
|
||||
func (a *Artifact) TotalSizeBytes() int64 {
|
||||
var s int64
|
||||
for _, artifact := range a.Manifests {
|
||||
for _, layer := range artifact.Layers {
|
||||
s += layer.Size
|
||||
}
|
||||
for _, layer := range a.Manifest.Layers {
|
||||
s += layer.Size
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -45,13 +45,7 @@ func (a *Artifact) SetName(name string) {
|
|||
}
|
||||
|
||||
func (a *Artifact) GetDigest() (*digest.Digest, error) {
|
||||
if len(a.Manifests) > 1 {
|
||||
return nil, fmt.Errorf("not supported: multiple manifests found in artifact")
|
||||
}
|
||||
if len(a.Manifests) < 1 {
|
||||
return nil, fmt.Errorf("not supported: no manifests found in artifact")
|
||||
}
|
||||
b, err := json.Marshal(a.Manifests[0])
|
||||
b, err := json.Marshal(a.Manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -72,17 +66,13 @@ func (al ArtifactList) GetByNameOrDigest(nameOrDigest string) (*Artifact, bool,
|
|||
}
|
||||
// Before giving up, check by digest
|
||||
for _, artifact := range al {
|
||||
// TODO Here we have to assume only a single manifest for the artifact; this will
|
||||
// need to evolve
|
||||
if len(artifact.Manifests) > 0 {
|
||||
artifactDigest, err := artifact.GetDigest()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// If the artifact's digest matches or is a prefix of ...
|
||||
if artifactDigest.Encoded() == nameOrDigest || strings.HasPrefix(artifactDigest.Encoded(), nameOrDigest) {
|
||||
return artifact, true, nil
|
||||
}
|
||||
artifactDigest, err := artifact.GetDigest()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// If the artifact's digest matches or is a prefix of ...
|
||||
if artifactDigest.Encoded() == nameOrDigest || strings.HasPrefix(artifactDigest.Encoded(), nameOrDigest) {
|
||||
return artifact, true, nil
|
||||
}
|
||||
}
|
||||
return nil, false, fmt.Errorf("no artifact found with name or digest of %s", nameOrDigest)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -160,7 +161,8 @@ func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimag
|
|||
|
||||
// Add takes one or more local files and adds them to the local artifact store. The empty
|
||||
// string input is for possible custom artifact types.
|
||||
func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, _ string) (*digest.Digest, error) {
|
||||
func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, options *libartTypes.AddOptions) (*digest.Digest, error) {
|
||||
annots := maps.Clone(options.Annotations)
|
||||
if len(dest) == 0 {
|
||||
return nil, ErrEmptyArtifactName
|
||||
}
|
||||
|
@ -191,6 +193,13 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, _
|
|||
defer imageDest.Close()
|
||||
|
||||
for _, path := range paths {
|
||||
// currently we don't allow override of the filename ; if a user requirement emerges,
|
||||
// we could seemingly accommodate but broadens possibilities of something bad happening
|
||||
// for things like `artifact extract`
|
||||
if _, hasTitle := options.Annotations[specV1.AnnotationTitle]; hasTitle {
|
||||
return nil, fmt.Errorf("cannot override filename with %s annotation", specV1.AnnotationTitle)
|
||||
}
|
||||
|
||||
// get the new artifact into the local store
|
||||
newBlobDigest, newBlobSize, err := layout.PutBlobFromLocalFile(ctx, imageDest, path)
|
||||
if err != nil {
|
||||
|
@ -200,14 +209,16 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, _
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newArtifactAnnotations := map[string]string{}
|
||||
newArtifactAnnotations[specV1.AnnotationTitle] = filepath.Base(path)
|
||||
|
||||
annots[specV1.AnnotationTitle] = filepath.Base(path)
|
||||
|
||||
newLayer := specV1.Descriptor{
|
||||
MediaType: detectedType,
|
||||
Digest: newBlobDigest,
|
||||
Size: newBlobSize,
|
||||
Annotations: newArtifactAnnotations,
|
||||
Annotations: annots,
|
||||
}
|
||||
|
||||
artifactManifestLayers = append(artifactManifestLayers, newLayer)
|
||||
}
|
||||
|
||||
|
@ -215,11 +226,12 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, _
|
|||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
MediaType: specV1.MediaTypeImageManifest,
|
||||
// TODO This should probably be configurable once the CLI is capable
|
||||
ArtifactType: "",
|
||||
Config: specV1.DescriptorEmptyJSON,
|
||||
Layers: artifactManifestLayers,
|
||||
Config: specV1.DescriptorEmptyJSON,
|
||||
Layers: artifactManifestLayers,
|
||||
}
|
||||
|
||||
artifactManifest.ArtifactType = options.ArtifactType
|
||||
|
||||
rawData, err := json.Marshal(artifactManifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -287,13 +299,13 @@ func (as ArtifactStore) getArtifacts(ctx context.Context, _ *libartTypes.GetArti
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifests, err := getManifests(ctx, imgSrc, nil)
|
||||
manifest, err := getManifest(ctx, imgSrc)
|
||||
imgSrc.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifact := libartifact.Artifact{
|
||||
Manifests: manifests,
|
||||
Manifest: manifest,
|
||||
}
|
||||
if val, ok := l.ManifestDescriptor.Annotations[specV1.AnnotationRefName]; ok {
|
||||
artifact.SetName(val)
|
||||
|
@ -304,41 +316,25 @@ func (as ArtifactStore) getArtifacts(ctx context.Context, _ *libartTypes.GetArti
|
|||
return al, nil
|
||||
}
|
||||
|
||||
// getManifests takes an imgSrc and starting digest (nil means "top") and collects all the manifests "under"
|
||||
// it. this func calls itself recursively with a new startingDigest assuming that we are dealing with
|
||||
// an index list
|
||||
func getManifests(ctx context.Context, imgSrc types.ImageSource, startingDigest *digest.Digest) ([]manifest.OCI1, error) {
|
||||
var (
|
||||
manifests []manifest.OCI1
|
||||
)
|
||||
b, manifestType, err := imgSrc.GetManifest(ctx, startingDigest)
|
||||
// getManifest takes an imgSrc and returns the manifest for the imgSrc.
|
||||
// A OCI index list is not supported and will return an error.
|
||||
func getManifest(ctx context.Context, imgSrc types.ImageSource) (*manifest.OCI1, error) {
|
||||
b, manifestType, err := imgSrc.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// this assumes that there are only single, and multi-images
|
||||
if !manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
// these are the keepers
|
||||
mani, err := manifest.OCI1FromManifest(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifests = append(manifests, *mani)
|
||||
return manifests, nil
|
||||
// We only support a single flat manifest and not an oci index list
|
||||
if manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
return nil, fmt.Errorf("manifest %q is index list", imgSrc.Reference().StringWithinTransport())
|
||||
}
|
||||
// We are dealing with an oci index list
|
||||
maniList, err := manifest.OCI1IndexFromManifest(b)
|
||||
|
||||
// parse the single manifest
|
||||
mani, err := manifest.OCI1FromManifest(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range maniList.Manifests {
|
||||
iterManifests, err := getManifests(ctx, imgSrc, &m.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifests = append(manifests, iterManifests...)
|
||||
}
|
||||
return manifests, nil
|
||||
return mani, nil
|
||||
}
|
||||
|
||||
func createEmptyStanza(path string) error {
|
||||
|
|
|
@ -3,3 +3,9 @@ package types
|
|||
// GetArtifactOptions is a struct containing options that for obtaining artifacts.
|
||||
// It is meant for future growth or changes required without wacking the API
|
||||
type GetArtifactOptions struct{}
|
||||
|
||||
// AddOptions are additional descriptors of an artifact file
|
||||
type AddOptions struct {
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
ArtifactType string `json:",omitempty"`
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package define
|
|||
import "net/url"
|
||||
|
||||
type InitOptions struct {
|
||||
PlaybookPath string
|
||||
CPUS uint64
|
||||
DiskSize uint64
|
||||
IgnitionPath string
|
||||
|
|
|
@ -11,19 +11,21 @@ import (
|
|||
|
||||
type initMachine struct {
|
||||
/*
|
||||
--cpus uint Number of CPUs (default 1)
|
||||
--disk-size uint Disk size in GiB (default 100)
|
||||
--ignition-path string Path to ignition file
|
||||
--username string Username of the remote user (default "core" for FCOS, "user" for Fedora)
|
||||
--image-path string Path to bootable image (default "testing")
|
||||
-m, --memory uint Memory in MiB (default 2048)
|
||||
--now Start machine now
|
||||
--rootful Whether this machine should prefer rootful container execution
|
||||
--timezone string Set timezone (default "local")
|
||||
-v, --volume stringArray Volumes to mount, source:target
|
||||
--volume-driver string Optional volume driver
|
||||
--cpus uint Number of CPUs (default 1)
|
||||
--disk-size uint Disk size in GiB (default 100)
|
||||
--ignition-path string Path to ignition file
|
||||
--username string Username of the remote user (default "core" for FCOS, "user" for Fedora)
|
||||
--image-path string Path to bootable image (default "testing")
|
||||
-m, --memory uint Memory in MiB (default 2048)
|
||||
--now Start machine now
|
||||
--rootful Whether this machine should prefer rootful container execution
|
||||
--playbook string Run an ansible playbook after first boot
|
||||
--timezone string Set timezone (default "local")
|
||||
-v, --volume stringArray Volumes to mount, source:target
|
||||
--volume-driver string Optional volume driver
|
||||
|
||||
*/
|
||||
playbook string
|
||||
cpus *uint
|
||||
diskSize *uint
|
||||
ignitionPath string
|
||||
|
@ -73,6 +75,9 @@ func (i *initMachine) buildCmd(m *machineTestBuilder) []string {
|
|||
if i.rootful {
|
||||
cmd = append(cmd, "--rootful")
|
||||
}
|
||||
if l := len(i.playbook); l > 0 {
|
||||
cmd = append(cmd, "--playbook", i.playbook)
|
||||
}
|
||||
if i.userModeNetworking {
|
||||
cmd = append(cmd, "--user-mode-networking")
|
||||
}
|
||||
|
@ -152,6 +157,11 @@ func (i *initMachine) withRootful(r bool) *initMachine {
|
|||
return i
|
||||
}
|
||||
|
||||
func (i *initMachine) withRunPlaybook(p string) *initMachine {
|
||||
i.playbook = p
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *initMachine) withUserModeNetworking(r bool) *initMachine { //nolint:unused
|
||||
i.userModeNetworking = r
|
||||
return i
|
||||
|
|
|
@ -98,6 +98,73 @@ var _ = Describe("podman machine init", func() {
|
|||
}
|
||||
})
|
||||
|
||||
It("run playbook", func() {
|
||||
str := randomString()
|
||||
|
||||
// ansible playbook file to create a text file containing a random string
|
||||
playbookContents := fmt.Sprintf(`- name: Simple podman machine example
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: create a file
|
||||
ansible.builtin.copy:
|
||||
dest: ~/foobar.txt
|
||||
content: "%s\n"`, str)
|
||||
|
||||
playbookPath := filepath.Join(GinkgoT().TempDir(), "playbook.yaml")
|
||||
|
||||
// create the playbook file
|
||||
playbookFile, err := os.Create(playbookPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer playbookFile.Close()
|
||||
|
||||
// write the desired contents into the file
|
||||
_, err = playbookFile.WriteString(playbookContents)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
|
||||
name := randomString()
|
||||
i := new(initMachine)
|
||||
session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath).withRunPlaybook(playbookPath).withNow()).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(session).To(Exit(0))
|
||||
|
||||
// ensure the contents of the playbook file didn't change when getting copied
|
||||
ssh := new(sshMachine)
|
||||
sshSession, err := mb.setName(name).setCmd(ssh.withSSHCommand([]string{"cat", "playbook.yaml"})).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(sshSession).To(Exit(0))
|
||||
Expect(sshSession.outputToStringSlice()).To(Equal(strings.Split(playbookContents, "\n")))
|
||||
|
||||
// wait until the playbook.service is done before checking to make sure the playbook was a success
|
||||
playbookFinished := false
|
||||
for range 900 {
|
||||
sshSession, err = mb.setName(name).setCmd(ssh.withSSHCommand([]string{"systemctl", "is-active", "playbook.service"})).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
if sshSession.outputToString() == "inactive" {
|
||||
playbookFinished = true
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if !playbookFinished {
|
||||
Fail("playbook.service did not finish")
|
||||
}
|
||||
|
||||
// output the contents of the file generated by the playbook
|
||||
guestUser := "core"
|
||||
if isWSL() {
|
||||
guestUser = "user"
|
||||
}
|
||||
sshSession, err = mb.setName(name).setCmd(ssh.withSSHCommand([]string{"cat", fmt.Sprintf("/home/%s/foobar.txt", guestUser)})).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(sshSession).To(Exit(0))
|
||||
|
||||
// check its the same as the random number or string that we generated
|
||||
Expect(sshSession.outputToString()).To(Equal(str))
|
||||
})
|
||||
|
||||
It("simple init with start", func() {
|
||||
i := initMachine{}
|
||||
session, err := mb.setCmd(i.withImage(mb.imagePath)).run()
|
||||
|
|
|
@ -685,6 +685,51 @@ done
|
|||
`
|
||||
}
|
||||
|
||||
func (i *IgnitionBuilder) AddPlaybook(contents string, destPath string, username string) error {
|
||||
// create the ignition file object
|
||||
f := File{
|
||||
Node: Node{
|
||||
Group: GetNodeGrp(username),
|
||||
Path: destPath,
|
||||
User: GetNodeUsr(username),
|
||||
},
|
||||
FileEmbedded1: FileEmbedded1{
|
||||
Append: nil,
|
||||
Contents: Resource{
|
||||
Source: EncodeDataURLPtr(contents),
|
||||
},
|
||||
Mode: IntToPtr(0744),
|
||||
},
|
||||
}
|
||||
|
||||
// call ignitionBuilder.WithFile
|
||||
// add the config file to the ignition object
|
||||
i.WithFile(f)
|
||||
|
||||
unit := parser.NewUnitFile()
|
||||
unit.Add("Unit", "After", "ready.service")
|
||||
unit.Add("Unit", "ConditionFirstBoot", "yes")
|
||||
unit.Add("Service", "Type", "oneshot")
|
||||
unit.Add("Service", "User", username)
|
||||
unit.Add("Service", "Group", username)
|
||||
unit.Add("Service", "ExecStart", fmt.Sprintf("ansible-playbook %s", destPath))
|
||||
unit.Add("Install", "WantedBy", "default.target")
|
||||
unitContents, err := unit.ToString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create a systemd service
|
||||
playbookUnit := Unit{
|
||||
Enabled: BoolToPtr(true),
|
||||
Name: "playbook.service",
|
||||
Contents: &unitContents,
|
||||
}
|
||||
i.WithUnit(playbookUnit)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetNetRecoveryUnitFile() *parser.UnitFile {
|
||||
recoveryUnit := parser.NewUnitFile()
|
||||
recoveryUnit.Add("Unit", "Description", "Verifies health of network and recovers if necessary")
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
@ -207,6 +208,32 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
|
|||
}
|
||||
}
|
||||
|
||||
if len(opts.PlaybookPath) > 0 {
|
||||
f, err := os.Open(opts.PlaybookPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read playbook: %w", err)
|
||||
}
|
||||
|
||||
playbookDest := fmt.Sprintf("/home/%s/%s", userName, "playbook.yaml")
|
||||
|
||||
if mp.VMType() != machineDefine.WSLVirt {
|
||||
err = ignBuilder.AddPlaybook(string(s), playbookDest, userName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mc.Ansible = &vmconfigs.AnsibleConfig{
|
||||
PlaybookPath: playbookDest,
|
||||
Contents: string(s),
|
||||
User: userName,
|
||||
}
|
||||
}
|
||||
|
||||
readyIgnOpts, err := mp.PrepareIgnition(mc, &ignBuilder)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -543,6 +570,16 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDe
|
|||
}
|
||||
}
|
||||
|
||||
isFirstBoot, err := mc.IsFirstBoot()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if mp.VMType() == machineDefine.WSLVirt && mc.Ansible != nil && isFirstBoot {
|
||||
if err := machine.CommonSSHSilent(mc.Ansible.User, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, []string{"ansible-playbook", mc.Ansible.PlaybookPath}); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Provider is responsible for waiting
|
||||
if mp.UseProviderNetworkSetup() {
|
||||
return nil
|
||||
|
|
|
@ -53,6 +53,8 @@ type MachineConfig struct {
|
|||
Starting bool
|
||||
|
||||
Rosetta bool
|
||||
|
||||
Ansible *AnsibleConfig
|
||||
}
|
||||
|
||||
type machineImage interface { //nolint:unused
|
||||
|
@ -148,3 +150,9 @@ type VMStats struct {
|
|||
// LastUp contains the last recorded uptime
|
||||
LastUp time.Time
|
||||
}
|
||||
|
||||
type AnsibleConfig struct {
|
||||
PlaybookPath string
|
||||
Contents string
|
||||
User string
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ func createKeys(mc *vmconfigs.MachineConfig, dist string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func configureSystem(mc *vmconfigs.MachineConfig, dist string) error {
|
||||
func configureSystem(mc *vmconfigs.MachineConfig, dist string, ansibleConfig *vmconfigs.AnsibleConfig) error {
|
||||
user := mc.SSH.RemoteUsername
|
||||
if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, mc.SSH.Port, mc.SSH.Port)); err != nil {
|
||||
return fmt.Errorf("could not configure SSH port for guest OS: %w", err)
|
||||
|
@ -167,6 +167,12 @@ func configureSystem(mc *vmconfigs.MachineConfig, dist string) error {
|
|||
return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err)
|
||||
}
|
||||
|
||||
if ansibleConfig != nil {
|
||||
if err := wslPipe(ansibleConfig.Contents, dist, "sh", "-c", fmt.Sprintf("cat > %s", ansibleConfig.PlaybookPath)); err != nil {
|
||||
return fmt.Errorf("could not generate playbook file for guest os: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
|
||||
if err := wslPipe(lingerService, dist, "sh", "-c", lingerCmd); err != nil {
|
||||
return fmt.Errorf("could not generate linger service for guest OS: %w", err)
|
||||
|
|
|
@ -68,7 +68,7 @@ func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConf
|
|||
}
|
||||
|
||||
fmt.Println("Configuring system...")
|
||||
if err = configureSystem(mc, dist); err != nil {
|
||||
if err = configureSystem(mc, dist, mc.Ansible); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -364,7 +364,6 @@ get_cmd_line_args (int *argc_out)
|
|||
static bool
|
||||
can_use_shortcut (char **argv)
|
||||
{
|
||||
cleanup_free char *argv0 = NULL;
|
||||
bool ret = true;
|
||||
int argc;
|
||||
|
||||
|
@ -372,8 +371,6 @@ can_use_shortcut (char **argv)
|
|||
return false;
|
||||
#endif
|
||||
|
||||
argv0 = argv[0];
|
||||
|
||||
if (strstr (argv[0], "podman") == NULL)
|
||||
return false;
|
||||
|
||||
|
@ -439,6 +436,7 @@ static void __attribute__((constructor)) init()
|
|||
const char *listen_fds;
|
||||
const char *listen_fdnames;
|
||||
cleanup_free char **argv = NULL;
|
||||
cleanup_free char *argv0 = NULL;
|
||||
cleanup_dir DIR *d = NULL;
|
||||
int argc;
|
||||
|
||||
|
@ -496,6 +494,8 @@ static void __attribute__((constructor)) init()
|
|||
fprintf(stderr, "cannot retrieve cmd line");
|
||||
_exit (EXIT_FAILURE);
|
||||
}
|
||||
// Even if unused, this is needed to ensure we properly free the memory
|
||||
argv0 = argv[0];
|
||||
|
||||
if (geteuid () != 0 || getenv ("_CONTAINERS_USERNS_CONFIGURED") == NULL)
|
||||
do_preexec_hooks(argv, argc);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
cdi devices
|
||||
===========
|
||||
|
||||
This test copies a CDI device file on a tmpfs mounted on /etc/cdi, then checks that the CDI device in the compose file is present in a container. The test is skipped when running as rootless.
|
||||
|
||||
Validation
|
||||
------------
|
||||
|
||||
* The CDI device is present in the container.
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"cdiVersion": "0.3.0",
|
||||
"kind": "vendor.com/device",
|
||||
"devices": [
|
||||
{
|
||||
"name": "myKmsg",
|
||||
"containerEdits": {
|
||||
"mounts": [
|
||||
{"hostPath": "/dev/kmsg", "containerPath": "/dev/kmsg1", "options": ["rw", "rprivate", "rbind"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
services:
|
||||
test:
|
||||
image: alpine
|
||||
command: ["top"]
|
||||
volumes:
|
||||
- /dev:/dev-host
|
||||
security_opt:
|
||||
- label=disable
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: cdi
|
||||
device_ids: ['vendor.com/device=myKmsg']
|
||||
capabilities: []
|
|
@ -0,0 +1,9 @@
|
|||
if is_rootless; then
|
||||
reason=" - can't write to /etc/cdi"
|
||||
_show_ok skip "$testname # skip$reason"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p /etc/cdi
|
||||
mount -t tmpfs tmpfs /etc/cdi
|
||||
cp device.json /etc/cdi
|
|
@ -0,0 +1,3 @@
|
|||
if ! is_rootless; then
|
||||
umount -l /etc/cdi
|
||||
fi
|
|
@ -0,0 +1,11 @@
|
|||
# -*- bash -*-
|
||||
|
||||
ctr_name="cdi_device-test-1"
|
||||
|
||||
podman exec "$ctr_name" sh -c 'stat -c "%t:%T" /dev-host/kmsg'
|
||||
|
||||
expected=$output
|
||||
|
||||
podman exec "$ctr_name" sh -c 'stat -c "%t:%T" /dev/kmsg1'
|
||||
|
||||
is "$output" "$expected" "$testname : device /dev/kmsg1 has the same rdev as /dev/kmsg on the host"
|
|
@ -22,7 +22,7 @@ var _ = Describe("Podman artifact", func() {
|
|||
artifact1File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File}...)
|
||||
add1 := podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File}...)
|
||||
|
||||
artifact2File, err := createArtifactFile(10240)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -43,6 +43,24 @@ var _ = Describe("Podman artifact", func() {
|
|||
// Make sure the names are what we expect
|
||||
Expect(output).To(ContainElement(artifact1Name))
|
||||
Expect(output).To(ContainElement(artifact2Name))
|
||||
|
||||
// Check default digest length (should be 12)
|
||||
defaultFormatSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls", "--format", "{{.Digest}}"}...)
|
||||
defaultOutput := defaultFormatSession.OutputToStringArray()[0]
|
||||
Expect(defaultOutput).To(HaveLen(12))
|
||||
|
||||
// Check with --no-trunc and verify the len of the digest is the same as the len what was returned when the artifact
|
||||
// was added
|
||||
noTruncSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls", "--no-trunc", "--format", "{{.Digest}}"}...)
|
||||
truncOutput := noTruncSession.OutputToStringArray()[0]
|
||||
Expect(truncOutput).To(HaveLen(len(add1.OutputToString())))
|
||||
|
||||
// check with --noheading and verify the header is not present through a line count AND substring match
|
||||
noHeaderSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls", "--noheading"}...)
|
||||
noHeaderOutput := noHeaderSession.OutputToStringArray()
|
||||
Expect(noHeaderOutput).To(HaveLen(2))
|
||||
Expect(noHeaderOutput).ToNot(ContainElement("REPOSITORY"))
|
||||
|
||||
})
|
||||
|
||||
It("podman artifact simple add", func() {
|
||||
|
@ -67,6 +85,31 @@ var _ = Describe("Podman artifact", func() {
|
|||
Expect(addAgain.ErrorToString()).To(Equal(fmt.Sprintf("Error: artifact %s already exists", artifact1Name)))
|
||||
})
|
||||
|
||||
It("podman artifact add with options", func() {
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
artifact1File, err := createArtifactFile(1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifactType := "octet/foobar"
|
||||
annotation1 := "color=blue"
|
||||
annotation2 := "flavor=lemon"
|
||||
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "add", "--type", artifactType, "--annotation", annotation1, "--annotation", annotation2, artifact1Name, artifact1File}...)
|
||||
inspectSingleSession := podmanTest.PodmanExitCleanly([]string{"artifact", "inspect", artifact1Name}...)
|
||||
a := libartifact.Artifact{}
|
||||
err = json.Unmarshal([]byte(inspectSingleSession.OutputToString()), &a)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
Expect(a.Manifest.ArtifactType).To(Equal(artifactType))
|
||||
Expect(a.Manifest.Layers[0].Annotations["color"]).To(Equal("blue"))
|
||||
Expect(a.Manifest.Layers[0].Annotations["flavor"]).To(Equal("lemon"))
|
||||
|
||||
failSession := podmanTest.Podman([]string{"artifact", "add", "--annotation", "org.opencontainers.image.title=foobar", "foobar", artifact1File})
|
||||
failSession.WaitWithDefaultTimeout()
|
||||
Expect(failSession).Should(Exit(125))
|
||||
Expect(failSession.ErrorToString()).Should(Equal("Error: cannot override filename with org.opencontainers.image.title annotation"))
|
||||
})
|
||||
|
||||
It("podman artifact add multiple", func() {
|
||||
artifact1File1, err := createArtifactFile(1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -85,11 +128,7 @@ var _ = Describe("Podman artifact", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
|
||||
var layerCount int
|
||||
for _, layer := range a.Manifests {
|
||||
layerCount += len(layer.Layers)
|
||||
}
|
||||
Expect(layerCount).To(Equal(2))
|
||||
Expect(a.Manifest.Layers).To(HaveLen(2))
|
||||
})
|
||||
|
||||
It("podman artifact push and pull", func() {
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/containers/podman/v5/pkg/inspect"
|
||||
. "github.com/containers/podman/v5/test/utils"
|
||||
"github.com/containers/podman/v5/utils"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/containers/storage/pkg/lockfile"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"github.com/containers/storage/pkg/stringid"
|
||||
|
@ -1187,19 +1188,21 @@ func (p *PodmanTestIntegration) makeOptions(args []string, options PodmanExecOpt
|
|||
}
|
||||
|
||||
func writeConf(conf []byte, confPath string) {
|
||||
if _, err := os.Stat(filepath.Dir(confPath)); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(confPath), 0o777); err != nil {
|
||||
GinkgoWriter.Println(err)
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(confPath, conf, 0o777); err != nil {
|
||||
GinkgoWriter.Println(err)
|
||||
}
|
||||
GinkgoHelper()
|
||||
err := os.MkdirAll(filepath.Dir(confPath), 0o755)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = ioutils.AtomicWriteFile(confPath, conf, 0o644)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func removeConf(confPath string) {
|
||||
if err := os.Remove(confPath); err != nil {
|
||||
GinkgoWriter.Println(err)
|
||||
GinkgoHelper()
|
||||
err := os.Remove(confPath)
|
||||
// Network remove test will remove the config and then this can fail.
|
||||
// If the config does not exists no reason to hard error here.
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -291,4 +291,16 @@ var _ = Describe("Podman Info", func() {
|
|||
Expect(info).ToNot(ExitCleanly())
|
||||
podmanTest.StartRemoteService() // Start service again so teardown runs clean
|
||||
})
|
||||
|
||||
It("Podman info: check client information", func() {
|
||||
info := podmanTest.Podman([]string{"info", "--format", "{{ .Client }}"})
|
||||
info.WaitWithDefaultTimeout()
|
||||
Expect(info).To(ExitCleanly())
|
||||
// client info should only appear when using the remote client
|
||||
if IsRemote() {
|
||||
Expect(info.OutputToString()).ToNot(Equal("<nil>"))
|
||||
} else {
|
||||
Expect(info.OutputToString()).To(Equal("<nil>"))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue