Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/podman/quadlet/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ podman quadlet install https://github.com/containers/podman/blob/main/test/e2e/q
func installFlags(cmd *cobra.Command) {
flags := cmd.Flags()
flags.BoolVar(&installOptions.ReloadSystemd, "reload-systemd", true, "Reload systemd after installing Quadlets")
flags.BoolVarP(&installOptions.Replace, "replace", "r", false, "Replace the installation even if the quadlet already exists")
}

func init() {
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman-quadlet-install.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ Reload systemd after installing Quadlets (default true).
In order to disable it users need to manually set the value
of this flag to `false`.

#### **--replace**, **-r**

Replace the Quadlet installation even if the generated unit file already exists (default false).
In order to enable it, users need to manually set the value
of this flag to `true`. This flag is used primarily to update an existing unit.

## EXAMPLES

Install quadlet from a file.
Expand Down
2 changes: 2 additions & 0 deletions pkg/domain/entities/quadlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package entities
type QuadletInstallOptions struct {
// Whether to reload systemd after installation is completed
ReloadSystemd bool
// Replace the installation even if the quadlet already exists
Replace bool
}

// QuadletInstallReport contains the output of the `quadlet install` command
Expand Down
16 changes: 11 additions & 5 deletions pkg/domain/infra/abi/quadlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (ic *ContainerEngine) QuadletInstall(ctx context.Context, pathsOrURLs []str
installReport.QuadletErrors[toInstall] = fmt.Errorf("populating temporary file: %w", err)
continue
}
installedPath, err := ic.installQuadlet(ctx, tmpFile.Name(), quadletFileName, installDir, assetFile, validateQuadletFile)
installedPath, err := ic.installQuadlet(ctx, tmpFile.Name(), quadletFileName, installDir, assetFile, validateQuadletFile, options.Replace)
if err != nil {
installReport.QuadletErrors[toInstall] = err
continue
Expand All @@ -210,7 +210,7 @@ func (ic *ContainerEngine) QuadletInstall(ctx context.Context, pathsOrURLs []str
continue
}
// If toInstall is a single file, execute the original logic
installedPath, err := ic.installQuadlet(ctx, toInstall, "", installDir, assetFile, validateQuadletFile)
installedPath, err := ic.installQuadlet(ctx, toInstall, "", installDir, assetFile, validateQuadletFile, options.Replace)
if err != nil {
installReport.QuadletErrors[toInstall] = err
continue
Expand Down Expand Up @@ -254,7 +254,7 @@ func getFileName(resp *http.Response, fileURL string) (string, error) {
// Perform some minimal validation, but not much.
// We can't know about a lot of problems without running the Quadlet binary, which we
// only want to do once.
func (ic *ContainerEngine) installQuadlet(_ context.Context, path, destName, installDir, assetFile string, isQuadletFile bool) (string, error) {
func (ic *ContainerEngine) installQuadlet(_ context.Context, path, destName, installDir, assetFile string, isQuadletFile, replace bool) (string, error) {
// First, validate that the source path exists and is a file
stat, err := os.Stat(path)
if err != nil {
Expand All @@ -274,9 +274,15 @@ func (ic *ContainerEngine) installQuadlet(_ context.Context, path, destName, ins
return "", fmt.Errorf("%q is not a supported Quadlet file type", filepath.Ext(finalPath))
}

file, err := os.OpenFile(finalPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
var osFlags = os.O_CREATE | os.O_WRONLY

if !replace {
osFlags |= os.O_EXCL
}

file, err := os.OpenFile(finalPath, osFlags, 0644)
if err != nil {
if errors.Is(err, fs.ErrExist) {
if errors.Is(err, fs.ErrExist) && !replace {
return "", fmt.Errorf("a Quadlet with name %s already exists, refusing to overwrite", filepath.Base(finalPath))
}
return "", fmt.Errorf("unable to open file %s: %w", filepath.Base(finalPath), err)
Expand Down
39 changes: 39 additions & 0 deletions test/system/253-podman-quadlet.bats
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,43 @@ EOF
assert $status -eq 0 "quadlet rm --ignore should succeed even for non-existent quadlets"
}

@test "quadlet install --replace" {
local install_dir=$(get_quadlet_install_dir)
# Create a test quadlet file
local quadlet_file=$PODMAN_TMPDIR/alpine-quadlet.container
local initial_exec='Exec=sh -c "echo STARTED CONTAINER; trap '\''exit'\'' SIGTERM; while :; do sleep 0.1; done"'
cat > $quadlet_file <<EOF
[Container]
Image=$IMAGE
$initial_exec
EOF
# Test quadlet install
run_podman quadlet install $quadlet_file
# Verify install output contains the quadlet name on a single line
assert "$output" =~ "alpine-quadlet.container" "install output should contain quadlet name"

# Without replace should fail
run_podman 125 quadlet install $quadlet_file
assert "$output" =~ "refusing to overwrite" "reinstall without --replace must fail with the overwrite error message"

cat > $quadlet_file <<EOF
[Container]
Image=$IMAGE
Exec=sh -c "echo STARTED CONTAINER UPDATED; trap 'exit' SIGTERM; while :; do sleep 0.1; done"
EOF
# With replace should pass and update quadlet
run_podman quadlet install --replace $quadlet_file

# Verify install output contains the quadlet name on a single line
assert "$output" =~ "alpine-quadlet.container" "install output should contain quadlet name"

run_podman quadlet print alpine-quadlet.container

assert "$output" !~ "$initial_exec" "Printed content must not show the initial version"
assert "$output" == "$(<$quadlet_file)" "Printed content must match the updated file content"

# Clean up
run_podman quadlet rm alpine-quadlet.container
}

# vim: filetype=sh