summaryrefslogtreecommitdiff
path: root/cmd/podman/auto-update.go
blob: 88ef0ec88533d9a13c1c11770eab0ba300a39e3e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/containers/common/pkg/auth"
	"github.com/containers/common/pkg/completion"
	"github.com/containers/common/pkg/report"
	"github.com/containers/podman/v4/cmd/podman/common"
	"github.com/containers/podman/v4/cmd/podman/registry"
	"github.com/containers/podman/v4/pkg/domain/entities"
	"github.com/containers/podman/v4/pkg/errorhandling"
	"github.com/spf13/cobra"
)

type cliAutoUpdateOptions struct {
	entities.AutoUpdateOptions
	format string
}

var (
	autoUpdateOptions     = cliAutoUpdateOptions{}
	autoUpdateDescription = `Auto update containers according to their auto-update policy.

  Auto-update policies are specified with the "io.containers.autoupdate" label.
  Containers are expected to run in systemd units created with "podman-generate-systemd --new",
  or similar units that create new containers in order to run the updated images.
  Please refer to the podman-auto-update(1) man page for details.`
	autoUpdateCommand = &cobra.Command{
		Annotations:       map[string]string{registry.EngineMode: registry.ABIMode},
		Use:               "auto-update [options]",
		Short:             "Auto update containers according to their auto-update policy",
		Long:              autoUpdateDescription,
		RunE:              autoUpdate,
		ValidArgsFunction: completion.AutocompleteNone,
		Example: `podman auto-update
  podman auto-update --authfile ~/authfile.json`,
	}
)

func init() {
	registry.Commands = append(registry.Commands, registry.CliCommand{
		Command: autoUpdateCommand,
	})

	flags := autoUpdateCommand.Flags()

	authfileFlagName := "authfile"
	flags.StringVar(&autoUpdateOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path to the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
	_ = autoUpdateCommand.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)

	flags.BoolVar(&autoUpdateOptions.DryRun, "dry-run", false, "Check for pending updates")
	flags.BoolVar(&autoUpdateOptions.Rollback, "rollback", true, "Rollback to previous image if update fails")

	flags.StringVar(&autoUpdateOptions.format, "format", "", "Change the output format to JSON or a Go template")
	_ = autoUpdateCommand.RegisterFlagCompletionFunc("format", common.AutocompleteFormat(&autoUpdateOutput{}))
}

func autoUpdate(cmd *cobra.Command, args []string) error {
	if len(args) > 0 {
		// Backwards compat. System tests expect this error string.
		return fmt.Errorf("`%s` takes no arguments", cmd.CommandPath())
	}

	allReports, failures := registry.ContainerEngine().AutoUpdate(registry.GetContext(), autoUpdateOptions.AutoUpdateOptions)
	if allReports == nil {
		return errorhandling.JoinErrors(failures)
	}

	if err := writeTemplate(allReports, autoUpdateOptions.format); err != nil {
		failures = append(failures, err)
	}

	return errorhandling.JoinErrors(failures)
}

type autoUpdateOutput struct {
	Unit          string
	Container     string
	ContainerName string
	ContainerID   string
	Image         string
	Policy        string
	Updated       string
}

func reportsToOutput(allReports []*entities.AutoUpdateReport) []autoUpdateOutput {
	output := make([]autoUpdateOutput, len(allReports))
	for i, r := range allReports {
		output[i] = autoUpdateOutput{
			Unit:          r.SystemdUnit,
			Container:     fmt.Sprintf("%s (%s)", r.ContainerID[:12], r.ContainerName),
			ContainerName: r.ContainerName,
			ContainerID:   r.ContainerID,
			Image:         r.ImageName,
			Policy:        r.Policy,
			Updated:       r.Updated,
		}
	}
	return output
}

func writeTemplate(allReports []*entities.AutoUpdateReport, inputFormat string) error {
	var format string
	var printHeader bool

	output := reportsToOutput(allReports)
	switch inputFormat {
	case "":
		rows := []string{"{{.Unit}}", "{{.Container}}", "{{.Image}}", "{{.Policy}}", "{{.Updated}}"}
		format = "{{range . }}" + strings.Join(rows, "\t") + "\n{{end -}}"
		printHeader = true
	case "json":
		prettyJSON, err := json.MarshalIndent(output, "", "    ")
		if err != nil {
			return err
		}
		fmt.Println(string(prettyJSON))
		return nil
	default:
		format = "{{range . }}" + inputFormat + "\n{{end -}}"
	}

	tmpl, err := report.NewTemplate("auto-update").Parse(format)
	if err != nil {
		return err
	}

	w, err := report.NewWriterDefault(os.Stdout)
	if err != nil {
		return err
	}
	defer w.Flush()

	if printHeader {
		headers := report.Headers(autoUpdateOutput{}, nil)
		if err := tmpl.Execute(w, headers); err != nil {
			return err
		}
	}

	return tmpl.Execute(w, output)
}