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.Force, "force", "f", false, "Force the installation even if the quadlet already exists")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if force means only to replace it if it already exists, then like @mheon said, --replace is probably more consistent with other commands?

}

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 @@ -22,6 +22,12 @@ Note: In case user wants to install Quadlet application then first path should b

## OPTIONS

#### **--force**, **-f**

Force 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.

#### **--reload-systemd**

Reload systemd after installing Quadlets (default true).
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
// Force the installation even if the quadlet already exists
Force bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, im ok with force here too

}

// 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.Force)
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.Force)
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, force 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 !force {
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) && !force {
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
38 changes: 38 additions & 0 deletions test/system/253-podman-quadlet.bats
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,42 @@ EOF
assert $status -eq 0 "quadlet rm --ignore should succeed even for non-existent quadlets"
}

@test "quadlet install --force" {
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 force should fail
run_podman 125 quadlet install $quadlet_file
assert "$output" =~ "refusing to overwrite" "reinstall without --force 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 force should pass and update quadlet
run_podman quadlet install --force $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"

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

# vim: filetype=sh