rework system connection and farm storage

We now no longer write containers.conf, instead system connections and
farms are written to a new file called podman-connections.conf.

This is a major rework and I had to change a lot of things to get this
to compile again with my c/common changes.

It is a breaking change for users as connections/farms added before this
commit can now no longer be removed or modified directly. However because
the logic keeps reading from containers.conf the old connections can
still be used to connect to a remote host.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2024-01-26 15:47:30 +01:00
parent 1698fa0ad8
commit 74454bf59c
48 changed files with 1092 additions and 1090 deletions

View File

@ -837,15 +837,15 @@ func AutoCompleteFarms(cmd *cobra.Command, args []string, toComplete string) ([]
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
suggestions := []string{}
cfg, err := config.ReadCustomConfig()
farms, err := podmanConfig.ContainersConfDefaultsRO.GetAllFarms()
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for k := range cfg.Farms.List {
suggestions = append(suggestions, k)
suggestions := make([]string, 0, len(farms))
for _, farm := range farms {
suggestions = append(suggestions, farm.Name)
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
@ -856,16 +856,17 @@ func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
suggestions := []string{}
cfg, err := config.ReadCustomConfig()
cons, err := podmanConfig.ContainersConfDefaultsRO.GetAllConnections()
if err != nil {
cobra.CompErrorln(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
for k, v := range cfg.Engine.ServiceDestinations {
suggestions := make([]string, 0, len(cons))
for _, con := range cons {
// the URI will be show as description in shells like zsh
suggestions = append(suggestions, k+"\t"+v.URI)
suggestions = append(suggestions, con.Name+"\t"+con.URI)
}
return suggestions, cobra.ShellCompDirectiveNoFileComp

View File

@ -14,7 +14,6 @@ import (
"strings"
"text/template"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/pkg/machine"
@ -114,15 +113,10 @@ func composeDockerHost() (string, error) {
return registry.DefaultAPIAddress(), nil
}
cfg, err := config.ReadCustomConfig()
// TODO need to add support for --connection and --url
connection, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetConnection("", true)
if err != nil {
return "", err
}
// NOTE: podman --connection=foo and --url=... are injected
// into the default connection below in `root.go`.
defaultConnection := cfg.Engine.ActiveService
if defaultConnection == "" {
logrus.Info(err)
switch runtime.GOOS {
// If no default connection is set on Linux or FreeBSD,
// we just use the local socket by default - just as
@ -137,10 +131,6 @@ func composeDockerHost() (string, error) {
}
}
connection, ok := cfg.Engine.ServiceDestinations[defaultConnection]
if !ok {
return "", fmt.Errorf("internal error: default connection %q not found in database", defaultConnection)
}
parsedConnection, err := url.Parse(connection.URI)
if err != nil {
return "", fmt.Errorf("preparing connection to remote machine: %w", err)

View File

@ -7,7 +7,6 @@ import (
"strings"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
@ -50,14 +49,8 @@ func init() {
cleanupFlag := "cleanup"
flags.BoolVar(&buildOpts.buildOptions.Cleanup, cleanupFlag, false, "Remove built images from farm nodes on success")
podmanConfig := registry.PodmanConfig()
farmFlagName := "farm"
// If remote, don't read the client's containers.conf file
defaultFarm := ""
if !registry.IsRemote() {
defaultFarm = podmanConfig.ContainersConfDefaultsRO.Farms.Default
}
flags.StringVar(&buildOpts.farm, farmFlagName, defaultFarm, "Farm to use for builds")
flags.StringVar(&buildOpts.farm, farmFlagName, "", "Farm to use for builds")
_ = buildCommand.RegisterFlagCompletionFunc(farmFlagName, common.AutoCompleteFarms)
localFlagName := "local"
@ -122,23 +115,9 @@ func build(cmd *cobra.Command, args []string) error {
}
opts.SkipTLSVerify = !tlsVerify
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
defaultFarm := cfg.Farms.Default
if cmd.Flags().Changed("farm") {
f, err := cmd.Flags().GetString("farm")
if err != nil {
return err
}
defaultFarm = f
}
localEngine := registry.ImageEngine()
ctx := registry.Context()
farm, err := farm.NewFarm(ctx, defaultFarm, localEngine, buildOpts.local)
farm, err := farm.NewFarm(ctx, buildOpts.farm, localEngine, buildOpts.local)
if err != nil {
return fmt.Errorf("initializing: %w", err)
}

View File

@ -41,43 +41,39 @@ func create(cmd *cobra.Command, args []string) error {
farmName := args[0]
connections := args[1:]
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
if _, ok := cfg.Farms.List[farmName]; ok {
// if farm exists return an error
return fmt.Errorf("farm with name %q already exists", farmName)
}
// Can create an empty farm without any connections
if len(connections) == 0 {
cfg.Farms.List[farmName] = []string{}
}
for _, c := range connections {
if _, ok := cfg.Engine.ServiceDestinations[c]; ok {
if slices.Contains(cfg.Farms.List[farmName], c) {
// Don't add duplicate connections to a farm
continue
}
cfg.Farms.List[farmName] = append(cfg.Farms.List[farmName], c)
} else {
return fmt.Errorf("cannot create farm, %q is not a system connection", c)
err := config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if _, ok := cfg.Farm.List[farmName]; ok {
// if farm exists return an error
return fmt.Errorf("farm with name %q already exists", farmName)
}
}
// If this is the first farm being created, set it as the default farm
if len(cfg.Farms.List) == 1 {
cfg.Farms.Default = farmName
}
// Can create an empty farm without any connections
if len(connections) == 0 {
cfg.Farm.List[farmName] = []string{}
}
err = cfg.Write()
for _, c := range connections {
if _, ok := cfg.Connection.Connections[c]; ok {
if slices.Contains(cfg.Farm.List[farmName], c) {
// Don't add duplicate connections to a farm
continue
}
cfg.Farm.List[farmName] = append(cfg.Farm.List[farmName], c)
} else {
return fmt.Errorf("cannot create farm, %q is not a system connection", c)
}
}
// If this is the first farm being created, set it as the default farm
if len(cfg.Farm.List) == 1 {
cfg.Farm.Default = farmName
}
return nil
})
if err != nil {
return err
}
fmt.Printf("Farm %q created\n", farmName)
return nil
}

View File

@ -46,50 +46,29 @@ func init() {
formatFlagName := "format"
flags.StringVar(&lsOpts.Format, formatFlagName, "", "Format farm output using Go template")
_ = lsCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&farmOut{}))
}
type farmOut struct {
Name string
Connections []string
Default bool
_ = lsCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&config.Farm{}))
}
func list(cmd *cobra.Command, args []string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
format := lsOpts.Format
if format == "" && len(args) > 0 {
format = "json"
}
rows := make([]farmOut, 0)
for k, v := range cfg.Farms.List {
defaultFarm := false
if k == cfg.Farms.Default {
defaultFarm = true
}
r := farmOut{
Name: k,
Connections: v,
Default: defaultFarm,
}
rows = append(rows, r)
farms, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetAllFarms()
if err != nil {
return err
}
sort.Slice(rows, func(i, j int) bool {
return rows[i].Name < rows[j].Name
sort.Slice(farms, func(i, j int) bool {
return farms[i].Name < farms[j].Name
})
rpt := report.New(os.Stdout, cmd.Name())
defer rpt.Flush()
if report.IsJSON(format) {
buf, err := registry.JSONLibrary().MarshalIndent(rows, "", " ")
buf, err := registry.JSONLibrary().MarshalIndent(farms, "", " ")
if err == nil {
fmt.Println(string(buf))
}
@ -100,7 +79,7 @@ func list(cmd *cobra.Command, args []string) error {
rpt, err = rpt.Parse(report.OriginUser, format)
} else {
rpt, err = rpt.Parse(report.OriginPodman,
"{{range .}}{{.Name}}\t{{.Connections}}\t{{.Default}}\n{{end -}}")
"{{range .}}{{.Name}}\t{{.Connections}}\t{{.Default}}\t{{.ReadWrite}}\n{{end -}}")
}
if err != nil {
return err
@ -111,11 +90,12 @@ func list(cmd *cobra.Command, args []string) error {
"Default": "Default",
"Connections": "Connections",
"Name": "Name",
"ReadWrite": "ReadWrite",
}})
if err != nil {
return err
}
}
return rpt.Execute(rows)
return rpt.Execute(farms)
}

View File

@ -43,58 +43,56 @@ func init() {
}
func rm(cmd *cobra.Command, args []string) error {
cfg, err := config.ReadCustomConfig()
deletedFarms := []string{}
err := config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if rmOpts.All {
cfg.Farm.List = make(map[string][]string)
cfg.Farm.Default = ""
return nil
}
// If the --all is not set, we require at least one arg
if len(args) == 0 {
return errors.New("requires at lease 1 arg(s), received 0")
}
if len(cfg.Farm.List) == 0 {
return errors.New("no existing farms; nothing to remove")
}
for _, k := range args {
if _, ok := cfg.Farm.List[k]; !ok {
logrus.Warnf("farm %q doesn't exist; nothing to remove", k)
continue
}
delete(cfg.Farm.List, k)
deletedFarms = append(deletedFarms, k)
if k == cfg.Farm.Default {
cfg.Farm.Default = ""
}
}
// Return error if none of the given farms were deleted
if len(deletedFarms) == 0 {
return fmt.Errorf("failed to delete farms %q", args)
}
// Set a new default farm if the current default farm has been removed
if cfg.Farm.Default == "" && cfg.Farm.List != nil {
for k := range cfg.Farm.List {
cfg.Farm.Default = k
break
}
}
return nil
})
if err != nil {
return err
}
if rmOpts.All {
cfg.Farms.List = make(map[string][]string)
cfg.Farms.Default = ""
if err := cfg.Write(); err != nil {
return err
}
fmt.Println("All farms have been deleted")
return nil
}
// If the --all is not set, we require at least one arg
if len(args) == 0 {
return errors.New("requires at lease 1 arg(s), received 0")
}
if len(cfg.Farms.List) == 0 {
return errors.New("no existing farms; nothing to remove")
}
deletedFarms := []string{}
for _, k := range args {
if _, ok := cfg.Farms.List[k]; !ok {
logrus.Warnf("farm %q doesn't exist; nothing to remove", k)
continue
}
delete(cfg.Farms.List, k)
deletedFarms = append(deletedFarms, k)
if k == cfg.Farms.Default {
cfg.Farms.Default = ""
}
}
// Return error if none of the given farms were deleted
if len(deletedFarms) == 0 {
return fmt.Errorf("failed to delete farms %q", args)
}
// Set a new default farm if the current default farm has been removed
if cfg.Farms.Default == "" && cfg.Farms.List != nil {
for k := range cfg.Farms.List {
cfg.Farms.Default = k
break
}
}
if err := cfg.Write(); err != nil {
return err
}
for _, k := range deletedFarms {
fmt.Printf("Farm %q deleted\n", k)
}

View File

@ -63,62 +63,59 @@ func farmUpdate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("nothing to update for farm %q, please use the --add, --remove, or --default flags to update a farm", farmName)
}
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
if len(cfg.Farms.List) == 0 {
return errors.New("no farms are created at this time, there is nothing to update")
}
if _, ok := cfg.Farms.List[farmName]; !ok {
return fmt.Errorf("cannot update farm, %q farm doesn't exist", farmName)
}
if defChanged {
// Change the default to the given farm if --default=true
if updateOpts.Default {
cfg.Farms.Default = farmName
} else {
// if --default=false, user doesn't want any farms to be default so clear the DefaultFarm
cfg.Farms.Default = ""
}
}
if val, ok := cfg.Farms.List[farmName]; ok {
cMap := make(map[string]int)
for _, c := range val {
cMap[c] = 0
err := config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if len(cfg.Farm.List) == 0 {
return errors.New("no farms are created at this time, there is nothing to update")
}
for _, cRemove := range updateOpts.Remove {
connections := cfg.Farms.List[farmName]
if slices.Contains(connections, cRemove) {
delete(cMap, cRemove)
if _, ok := cfg.Farm.List[farmName]; !ok {
return fmt.Errorf("cannot update farm, %q farm doesn't exist", farmName)
}
if defChanged {
// Change the default to the given farm if --default=true
if updateOpts.Default {
cfg.Farm.Default = farmName
} else {
return fmt.Errorf("cannot remove from farm, %q is not a connection in the farm", cRemove)
// if --default=false, user doesn't want any farms to be default so clear the DefaultFarm
cfg.Farm.Default = ""
}
}
for _, cAdd := range updateOpts.Add {
if _, ok := cfg.Engine.ServiceDestinations[cAdd]; ok {
if _, ok := cMap[cAdd]; !ok {
cMap[cAdd] = 0
if val, ok := cfg.Farm.List[farmName]; ok {
cMap := make(map[string]int)
for _, c := range val {
cMap[c] = 0
}
for _, cRemove := range updateOpts.Remove {
connections := cfg.Farm.List[farmName]
if slices.Contains(connections, cRemove) {
delete(cMap, cRemove)
} else {
return fmt.Errorf("cannot remove from farm, %q is not a connection in the farm", cRemove)
}
} else {
return fmt.Errorf("cannot add to farm, %q is not a system connection", cAdd)
}
}
updatedConnections := []string{}
for k := range cMap {
updatedConnections = append(updatedConnections, k)
}
cfg.Farms.List[farmName] = updatedConnections
}
for _, cAdd := range updateOpts.Add {
if _, ok := cfg.Connection.Connections[cAdd]; ok {
if _, ok := cMap[cAdd]; !ok {
cMap[cAdd] = 0
}
} else {
return fmt.Errorf("cannot add to farm, %q is not a system connection", cAdd)
}
}
if err := cfg.Write(); err != nil {
updatedConnections := []string{}
for k := range cMap {
updatedConnections = append(updatedConnections, k)
}
cfg.Farm.List[farmName] = updatedConnections
}
return nil
})
if err != nil {
return err
}
fmt.Printf("Farm %q updated\n", farmName)

View File

@ -8,7 +8,6 @@ import (
"runtime"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
@ -108,16 +107,18 @@ func hostInfo() (*entities.MachineHostInfo, error) {
host.NumberOfMachines = len(listResponse)
cfg, err := config.ReadCustomConfig()
if err != nil {
return nil, err
defaultCon := ""
con, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetConnection("", true)
if err == nil {
// ignore the error here we only want to know if we have a default connection to show it in list
defaultCon = con.Name
}
// Default state of machine is stopped
host.MachineState = "Stopped"
for _, vm := range listResponse {
// Set default machine if found
if vm.Name == cfg.Engine.ActiveService {
if vm.Name == defaultCon {
host.DefaultMachine = vm.Name
}
// If machine is running or starting, it is automatically the current machine

View File

@ -7,7 +7,6 @@ import (
"os"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/machine"
@ -150,15 +149,18 @@ func initMachine(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%s: %w", initOpts.Name, machine.ErrVMAlreadyExists)
}
cfg, err := config.ReadCustomConfig()
// check if a system connection already exists
cons, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetAllConnections()
if err != nil {
return err
}
// check if a system connection already exists
for _, connection := range []string{initOpts.Name, fmt.Sprintf("%s-root", initOpts.Name)} {
if _, valueFound := cfg.Engine.ServiceDestinations[connection]; valueFound {
return fmt.Errorf("system connection %q already exists. consider a different machine name or remove the connection with `podman system connection rm`", connection)
for _, con := range cons {
if con.ReadWrite {
for _, connection := range []string{initOpts.Name, fmt.Sprintf("%s-root", initOpts.Name)} {
if con.Name == connection {
return fmt.Errorf("system connection %q already exists. consider a different machine name or remove the connection with `podman system connection rm`", connection)
}
}
}
}

View File

@ -10,7 +10,6 @@ import (
"time"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
@ -79,12 +78,15 @@ func list(cmd *cobra.Command, args []string) error {
return listResponse[i].Running
})
if report.IsJSON(listFlag.format) {
machineReporter, err := toMachineFormat(listResponse)
if err != nil {
return err
}
defaultCon := ""
con, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetConnection("", true)
if err == nil {
// ignore the error here we only want to know if we have a default connection to show it in list
defaultCon = con.Name
}
if report.IsJSON(listFlag.format) {
machineReporter := toMachineFormat(listResponse, defaultCon)
b, err := json.MarshalIndent(machineReporter, "", " ")
if err != nil {
return err
@ -93,11 +95,7 @@ func list(cmd *cobra.Command, args []string) error {
return nil
}
machineReporter, err := toHumanFormat(listResponse)
if err != nil {
return err
}
machineReporter := toHumanFormat(listResponse, defaultCon)
return outputTemplate(cmd, machineReporter)
}
@ -153,16 +151,11 @@ func streamName(imageStream string) string {
return imageStream
}
func toMachineFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error) {
cfg, err := config.ReadCustomConfig()
if err != nil {
return nil, err
}
func toMachineFormat(vms []*machine.ListResponse, defaultCon string) []*entities.ListReporter {
machineResponses := make([]*entities.ListReporter, 0, len(vms))
for _, vm := range vms {
response := new(entities.ListReporter)
response.Default = vm.Name == cfg.Engine.ActiveService
response.Default = vm.Name == defaultCon
response.Name = vm.Name
response.Running = vm.Running
response.LastUp = strTime(vm.LastUp)
@ -180,19 +173,14 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, err
machineResponses = append(machineResponses, response)
}
return machineResponses, nil
return machineResponses
}
func toHumanFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error) {
cfg, err := config.ReadCustomConfig()
if err != nil {
return nil, err
}
func toHumanFormat(vms []*machine.ListResponse, defaultCon string) []*entities.ListReporter {
humanResponses := make([]*entities.ListReporter, 0, len(vms))
for _, vm := range vms {
response := new(entities.ListReporter)
if vm.Name == cfg.Engine.ActiveService {
if vm.Name == defaultCon {
response.Name = vm.Name + "*"
response.Default = true
} else {
@ -218,5 +206,5 @@ func toHumanFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error
humanResponses = append(humanResponses, response)
}
return humanResponses, nil
return humanResponses
}

View File

@ -7,7 +7,6 @@ import (
"net/url"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/pkg/machine"
@ -93,12 +92,12 @@ func ssh(cmd *cobra.Command, args []string) error {
}
func remoteConnectionUsername() (string, error) {
cfg, err := config.ReadCustomConfig()
con, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetConnection("", true)
if err != nil {
return "", err
}
dest := cfg.Engine.ServiceDestinations[cfg.Engine.ActiveService].URI
uri, err := url.Parse(dest)
uri, err := url.Parse(con.URI)
if err != nil {
return "", err
}

View File

@ -143,7 +143,7 @@ func Execute() {
}
// readRemoteCliFlags reads cli flags related to operating podman remotely
func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) (err error) {
func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) error {
conf := podmanConfig.ContainersConfDefaultsRO
contextConn, host := cmd.Root().LocalFlags().Lookup("context"), cmd.Root().LocalFlags().Lookup("host")
conn, url := cmd.Root().LocalFlags().Lookup("connection"), cmd.Root().LocalFlags().Lookup("url")
@ -151,35 +151,32 @@ func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig)
switch {
case conn != nil && conn.Changed:
if contextConn != nil && contextConn.Changed {
err = fmt.Errorf("use of --connection and --context at the same time is not allowed")
return
return fmt.Errorf("use of --connection and --context at the same time is not allowed")
}
if dest, ok := conf.Engine.ServiceDestinations[conn.Value.String()]; ok {
podmanConfig.URI = dest.URI
podmanConfig.Identity = dest.Identity
podmanConfig.MachineMode = dest.IsMachine
return
con, err := conf.GetConnection(conn.Value.String(), false)
if err != nil {
return err
}
err = fmt.Errorf("connection %q not found", conn.Value.String())
return
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.MachineMode = con.IsMachine
case url.Changed:
podmanConfig.URI = url.Value.String()
return
case contextConn != nil && contextConn.Changed:
service := contextConn.Value.String()
if service != "default" {
if dest, ok := conf.Engine.ServiceDestinations[contextConn.Value.String()]; ok {
podmanConfig.URI = dest.URI
podmanConfig.Identity = dest.Identity
podmanConfig.MachineMode = dest.IsMachine
return
con, err := conf.GetConnection(service, false)
if err != nil {
return err
}
return fmt.Errorf("connection %q not found", service)
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.MachineMode = con.IsMachine
}
case host.Changed:
podmanConfig.URI = host.Value.String()
}
return
return nil
}
// setupRemoteConnection returns information about the active service destination
@ -191,28 +188,30 @@ func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig)
func setupRemoteConnection(podmanConfig *entities.PodmanConfig) error {
conf := podmanConfig.ContainersConfDefaultsRO
connEnv, hostEnv, sshkeyEnv := os.Getenv("CONTAINER_CONNECTION"), os.Getenv("CONTAINER_HOST"), os.Getenv("CONTAINER_SSHKEY")
dest, destFound := conf.Engine.ServiceDestinations[conf.Engine.ActiveService]
switch {
case connEnv != "":
if ConnEnvDest, ok := conf.Engine.ServiceDestinations[connEnv]; ok {
podmanConfig.URI = ConnEnvDest.URI
podmanConfig.Identity = ConnEnvDest.Identity
podmanConfig.MachineMode = ConnEnvDest.IsMachine
return nil
con, err := conf.GetConnection(connEnv, false)
if err != nil {
return err
}
return fmt.Errorf("connection %q not found", connEnv)
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.MachineMode = con.IsMachine
case hostEnv != "":
if sshkeyEnv != "" {
podmanConfig.Identity = sshkeyEnv
}
podmanConfig.URI = hostEnv
case destFound:
podmanConfig.URI = dest.URI
podmanConfig.Identity = dest.Identity
podmanConfig.MachineMode = dest.IsMachine
default:
podmanConfig.URI = registry.DefaultAPIAddress()
con, err := conf.GetConnection("", true)
if err == nil {
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.MachineMode = con.IsMachine
} else {
podmanConfig.URI = registry.DefaultAPIAddress()
}
}
return nil
}

View File

@ -176,49 +176,39 @@ func add(cmd *cobra.Command, args []string) error {
logrus.Warnf("%q unknown scheme, no validation provided", uri.Scheme)
}
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
if cmd.Flags().Changed("default") {
if cOpts.Default {
cfg.Engine.ActiveService = args[0]
}
}
dst := config.Destination{
URI: uri.String(),
URI: uri.String(),
Identity: cOpts.Identity,
}
if cmd.Flags().Changed("identity") {
dst.Identity = cOpts.Identity
}
if cfg.Engine.ServiceDestinations == nil {
cfg.Engine.ServiceDestinations = map[string]config.Destination{
args[0]: dst,
connection := args[0]
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if cOpts.Default {
cfg.Connection.Default = connection
}
cfg.Engine.ActiveService = args[0]
} else {
cfg.Engine.ServiceDestinations[args[0]] = dst
}
if cOpts.Farm != "" {
if cfg.Farms.List == nil {
cfg.Farms.List = map[string][]string{
cOpts.Farm: {args[0]},
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
connection: dst,
}
cfg.Farms.Default = cOpts.Farm
cfg.Connection.Default = connection
} else {
if val, ok := cfg.Farms.List[cOpts.Farm]; ok {
cfg.Farms.List[cOpts.Farm] = append(val, args[0])
cfg.Connection.Connections[connection] = dst
}
// Create or update an existing farm with the connection being added
if cOpts.Farm != "" {
if len(cfg.Farm.List) == 0 {
cfg.Farm.Default = cOpts.Farm
}
if val, ok := cfg.Farm.List[cOpts.Farm]; ok {
cfg.Farm.List[cOpts.Farm] = append(val, connection)
} else {
cfg.Farms.List[cOpts.Farm] = []string{args[0]}
cfg.Farm.List[cOpts.Farm] = []string{connection}
}
}
}
return cfg.Write()
return nil
})
}
func create(cmd *cobra.Command, args []string) error {
@ -237,24 +227,21 @@ func create(cmd *cobra.Command, args []string) error {
return err
}
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
dst := config.Destination{
URI: uri.String(),
}
if cfg.Engine.ServiceDestinations == nil {
cfg.Engine.ServiceDestinations = map[string]config.Destination{
args[0]: dst,
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
args[0]: dst,
}
cfg.Connection.Default = args[0]
} else {
cfg.Connection.Connections[args[0]] = dst
}
cfg.Engine.ActiveService = args[0]
} else {
cfg.Engine.ServiceDestinations[args[0]] = dst
}
return cfg.Write()
return nil
})
}
func translateDest(path string) (string, error) {

View File

@ -45,15 +45,13 @@ func init() {
}
func defaultRunE(cmd *cobra.Command, args []string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
connection := args[0]
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if _, found := cfg.Connection.Connections[connection]; !found {
return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", connection)
}
if _, found := cfg.Engine.ServiceDestinations[args[0]]; !found {
return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", args[0])
}
cfg.Engine.ActiveService = args[0]
return cfg.Write()
cfg.Connection.Default = connection
return nil
})
}

View File

@ -41,7 +41,7 @@ var (
func init() {
initFlags := func(cmd *cobra.Command) {
cmd.Flags().StringP("format", "f", "", "Custom Go template for printing connections")
_ = cmd.RegisterFlagCompletionFunc("format", common.AutocompleteFormat(&namedDestination{}))
_ = cmd.RegisterFlagCompletionFunc("format", common.AutocompleteFormat(&config.Connection{}))
cmd.Flags().BoolP("quiet", "q", false, "Custom Go template for printing connections")
}
@ -62,22 +62,11 @@ func init() {
initFlags(inspectCmd)
}
type namedDestination struct {
Name string
config.Destination
Default bool
}
func list(cmd *cobra.Command, _ []string) error {
return inspect(cmd, nil)
}
func inspect(cmd *cobra.Command, args []string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
format := cmd.Flag("format").Value.String()
if format == "" && args != nil {
format = "json"
@ -87,31 +76,23 @@ func inspect(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
rows := make([]namedDestination, 0)
for k, v := range cfg.Engine.ServiceDestinations {
if args != nil && !slices.Contains(args, k) {
cons, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetAllConnections()
if err != nil {
return err
}
rows := make([]config.Connection, 0, len(cons))
for _, con := range cons {
if args != nil && !slices.Contains(args, con.Name) {
continue
}
if quiet {
fmt.Println(k)
fmt.Println(con.Name)
continue
}
def := false
if k == cfg.Engine.ActiveService {
def = true
}
r := namedDestination{
Name: k,
Destination: config.Destination{
Identity: v.Identity,
URI: v.URI,
IsMachine: v.IsMachine,
},
Default: def,
}
rows = append(rows, r)
rows = append(rows, con)
}
if quiet {
@ -137,7 +118,7 @@ func inspect(cmd *cobra.Command, args []string) error {
rpt, err = rpt.Parse(report.OriginUser, format)
} else {
rpt, err = rpt.Parse(report.OriginPodman,
"{{range .}}{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.Default}}\n{{end -}}")
"{{range .}}{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.Default}}\t{{.ReadWrite}}\n{{end -}}")
}
if err != nil {
return err
@ -145,10 +126,11 @@ func inspect(cmd *cobra.Command, args []string) error {
if rpt.RenderHeaders {
err = rpt.Execute([]map[string]string{{
"Default": "Default",
"Identity": "Identity",
"Name": "Name",
"URI": "URI",
"Default": "Default",
"Identity": "Identity",
"Name": "Name",
"URI": "URI",
"ReadWrite": "ReadWrite",
}})
if err != nil {
return err

View File

@ -48,43 +48,35 @@ func init() {
}
func rm(cmd *cobra.Command, args []string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if rmOpts.All {
cfg.Connection.Connections = nil
cfg.Connection.Default = ""
if rmOpts.All {
for k := range cfg.Engine.ServiceDestinations {
delete(cfg.Engine.ServiceDestinations, k)
// Clear all the connections in any existing farms
for k := range cfg.Farm.List {
cfg.Farm.List[k] = []string{}
}
return nil
}
cfg.Engine.ActiveService = ""
// Clear all the connections in any existing farms
for k := range cfg.Farms.List {
cfg.Farms.List[k] = []string{}
if len(args) != 1 {
return errors.New("accepts 1 arg(s), received 0")
}
return cfg.Write()
}
if len(args) != 1 {
return errors.New("accepts 1 arg(s), received 0")
}
if cfg.Engine.ServiceDestinations != nil {
delete(cfg.Engine.ServiceDestinations, args[0])
}
if cfg.Engine.ActiveService == args[0] {
cfg.Engine.ActiveService = ""
}
// If there are existing farm, remove the deleted connection that might be part of a farm
for k, v := range cfg.Farms.List {
index := slices.Index(v, args[0])
if index > -1 {
cfg.Farms.List[k] = append(v[:index], v[index+1:]...)
delete(cfg.Connection.Connections, args[0])
if cfg.Connection.Default == args[0] {
cfg.Connection.Default = ""
}
}
return cfg.Write()
// If there are existing farm, remove the deleted connection that might be part of a farm
for k, v := range cfg.Farm.List {
index := slices.Index(v, args[0])
if index > -1 {
cfg.Farm.List[k] = append(v[:index], v[index+1:]...)
}
}
return nil
})
}

View File

@ -33,21 +33,18 @@ func init() {
}
func rename(cmd *cobra.Command, args []string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if _, found := cfg.Connection.Connections[args[0]]; !found {
return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", args[0])
}
if _, found := cfg.Engine.ServiceDestinations[args[0]]; !found {
return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", args[0])
}
cfg.Connection.Connections[args[1]] = cfg.Connection.Connections[args[0]]
delete(cfg.Connection.Connections, args[0])
cfg.Engine.ServiceDestinations[args[1]] = cfg.Engine.ServiceDestinations[args[0]]
delete(cfg.Engine.ServiceDestinations, args[0])
if cfg.Connection.Default == args[0] {
cfg.Connection.Default = args[1]
}
if cfg.Engine.ActiveService == args[0] {
cfg.Engine.ActiveService = args[1]
}
return cfg.Write()
return nil
})
}

View File

@ -18,18 +18,19 @@ List all the existing farms.
Change the default output format. This can be of a supported type like 'json' or a Go template.
Valid placeholders for the Go template listed below:
| **Placeholder** | **Description** |
| --------------- | ------------------------------------------ |
| .Connections | List of all system connections in the farm |
| .Default | Indicates whether farm is the default |
| .Name | Farm name |
| **Placeholder** | **Description** |
| --------------- | --------------------------------------------------------------------- |
| .Connections | List of all system connections in the farm |
| .Default | Indicates whether farm is the default |
| .Name | Farm name |
| .ReadWrite | Indicates if this farm can be modified using the podman farm commands |
## EXAMPLE
```
$ podman farm list
Name Connections Default
farm1 [f38 f37] false
farm2 [f37] true
Name Connections Default ReadWrite
farm1 [f38 f37] false true
farm2 [f37] true true
```
## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-farm(1)](podman-farm.1.md)**

View File

@ -23,6 +23,7 @@ Valid placeholders for the Go template listed below:
| .Default | Indicates whether connection is the default |
| .Identity | Path to file containing SSH identity |
| .Name | Connection Name/Identifier |
| .ReadWrite | Indicates if this connection can be modified using the system connection commands |
| .URI | URI to podman service. Valid schemes are ssh://[user@]*host*[:port]*Unix domain socket*[?secure=True], unix://*Unix domain socket*, and tcp://localhost[:*port*] |
#### **--quiet**, **-q**
@ -32,9 +33,9 @@ Only show connection names
## EXAMPLE
```
$ podman system connection list
Name URI Identity Default
devl ssh://root@example.com:/run/podman/podman.sock ~/.ssh/id_rsa True
devl ssh://user@example.com:/run/user/1000/podman/podman.sock ~/.ssh/id_rsa False
Name URI Identity Default ReadWrite
deva ssh://root@example.com:/run/podman/podman.sock ~/.ssh/id_rsa true true
devb ssh://user@example.com:/run/user/1000/podman/podman.sock ~/.ssh/id_rsa false true
```
## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-system(1)](podman-system.1.md)**, **[podman-system-connection(1)](podman-system-connection.1.md)**

View File

@ -252,6 +252,11 @@ Set default `--url` value to access Podman service. Automatically enables --remo
Set default `--identity` path to ssh key file value used to access Podman service.
#### **PODMAN_CONNECTIONS_CONF**
The path to the file where the system connections and farms created with `podman system connection add`
and `podman farm add` are stored, by default it uses `~/.config/containers/podman-connections.conf`.
#### **STORAGE_DRIVER**
Set default `--storage-driver` value.

6
go.mod
View File

@ -11,14 +11,14 @@ require (
github.com/checkpoint-restore/go-criu/v7 v7.0.0
github.com/containernetworking/plugins v1.4.0
github.com/containers/buildah v1.33.2-0.20231121195905-d1a1c53c8e1c
github.com/containers/common v0.57.1-0.20240129201029-3310a75e3608
github.com/containers/common v0.57.1-0.20240130143645-b26099256b92
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.7.2
github.com/containers/image/v5 v5.29.2-0.20240129204525-816800b5daf7
github.com/containers/image/v5 v5.29.2-0.20240130233108-e66a1ade2efc
github.com/containers/libhvee v0.6.0
github.com/containers/ocicrypt v1.1.9
github.com/containers/psgo v1.8.0
github.com/containers/storage v1.52.1-0.20240129173630-7a525ce0e2bc
github.com/containers/storage v1.52.1-0.20240130205044-62997abeaf2f
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09
github.com/coreos/stream-metadata-go v0.4.4
github.com/crc-org/vfkit v0.5.0

12
go.sum
View File

@ -257,14 +257,14 @@ github.com/containernetworking/plugins v1.4.0 h1:+w22VPYgk7nQHw7KT92lsRmuToHvb7w
github.com/containernetworking/plugins v1.4.0/go.mod h1:UYhcOyjefnrQvKvmmyEKsUA+M9Nfn7tqULPpH0Pkcj0=
github.com/containers/buildah v1.33.2-0.20231121195905-d1a1c53c8e1c h1:E7nxvH3N3kpyson0waJv1X+eY9hAs+x2zQswsK+//yY=
github.com/containers/buildah v1.33.2-0.20231121195905-d1a1c53c8e1c/go.mod h1:oMNfVrZGEfWVOxXTNOYPMdZzDfSo2umURK/TO0d8TRk=
github.com/containers/common v0.57.1-0.20240129201029-3310a75e3608 h1:P+3HLEBeuFr/XcFtyZIS8uk5zdbcvtrHKCSzHlUUbdY=
github.com/containers/common v0.57.1-0.20240129201029-3310a75e3608/go.mod h1:Na7hGh5WnmB0RdGkKyb6JQb6DtKrs5qoIGrPucuR8t0=
github.com/containers/common v0.57.1-0.20240130143645-b26099256b92 h1:Q60+ofGhDjVxY5lvYmmcVN8aeS9gtQ6pAn/pyLh7rRM=
github.com/containers/common v0.57.1-0.20240130143645-b26099256b92/go.mod h1:Na7hGh5WnmB0RdGkKyb6JQb6DtKrs5qoIGrPucuR8t0=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/gvisor-tap-vsock v0.7.2 h1:6CyU5D85C0/DciRRd7W0bPljK4FAS+DPrrHEQMHfZKY=
github.com/containers/gvisor-tap-vsock v0.7.2/go.mod h1:6NiTxh2GCVxZQLPzfuEB78/Osp2Usd9uf6nLdd6PiUY=
github.com/containers/image/v5 v5.29.2-0.20240129204525-816800b5daf7 h1:xnnFpYdUjDn2MZYXmvcopGEvWVO/J0V8OexEnlnIGf0=
github.com/containers/image/v5 v5.29.2-0.20240129204525-816800b5daf7/go.mod h1:op1w+ftmdbE3UNW/6as8mruL1i5AMq6nH+btNOykMcU=
github.com/containers/image/v5 v5.29.2-0.20240130233108-e66a1ade2efc h1:3I5+mrrG7Fuv4aA13t1hAMQcjN3rTAQInfbxa5P+XH4=
github.com/containers/image/v5 v5.29.2-0.20240130233108-e66a1ade2efc/go.mod h1:oMMRA6avp1Na54lVPCj/OvcfXDMLlzfy3H7xeRiWmmI=
github.com/containers/libhvee v0.6.0 h1:tUzwSz8R0GjR6IctgDnkTMjdtCk5Mxhpai4Vyv6UeF4=
github.com/containers/libhvee v0.6.0/go.mod h1:f/q1wCdQqOLiK3IZqqBfOD7exMZYBU5pDYsrMa/pSFg=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
@ -279,8 +279,8 @@ github.com/containers/ocicrypt v1.1.9/go.mod h1:dTKx1918d8TDkxXvarscpNVY+lyPakPN
github.com/containers/psgo v1.8.0 h1:2loGekmGAxM9ir5OsXWEfGwFxorMPYnc6gEDsGFQvhY=
github.com/containers/psgo v1.8.0/go.mod h1:T8ZxnX3Ur4RvnhxFJ7t8xJ1F48RhiZB4rSrOaR/qGHc=
github.com/containers/storage v1.43.0/go.mod h1:uZ147thiIFGdVTjMmIw19knttQnUCl3y9zjreHrg11s=
github.com/containers/storage v1.52.1-0.20240129173630-7a525ce0e2bc h1:Cfv5hg2OtNZnIZLfOVmLjObX0DuJb+aqkTl+2+dNweA=
github.com/containers/storage v1.52.1-0.20240129173630-7a525ce0e2bc/go.mod h1:T/ZMocbhShnMLIF0pdkiLPwpkwlGlyUWJeSXnfC/uew=
github.com/containers/storage v1.52.1-0.20240130205044-62997abeaf2f h1:BJSLHe7f1tgu53d8mGIK/y2KhEev5lggWlIk1rWYT7k=
github.com/containers/storage v1.52.1-0.20240130205044-62997abeaf2f/go.mod h1:T/ZMocbhShnMLIF0pdkiLPwpkwlGlyUWJeSXnfC/uew=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=

View File

@ -33,11 +33,6 @@ func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode
return nil, nil, nil, nil, err
}
confR, err := config.New(nil) // create a hand made config for the remote engine since we might use remote and native at once
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("could not make config: %w", err)
}
locations := []*entities.ImageScpOptions{}
cliConnections := []string{}
args := []string{src}
@ -84,17 +79,15 @@ func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode
cliConnections = []string{}
}
cfg, err := config.ReadCustomConfig() // get ready to set ssh destination if necessary
cfg, err := config.Default()
if err != nil {
return nil, nil, nil, nil, err
}
var serv map[string]config.Destination
serv, err = GetServiceInformation(&sshInfo, cliConnections, cfg)
err = GetServiceInformation(&sshInfo, cliConnections, cfg)
if err != nil {
return nil, nil, nil, nil, err
}
confR.Engine = config.EngineConfig{Remote: true, CgroupManager: "cgroupfs", ServiceDestinations: serv} // pass the service dest (either remote or something else) to engine
saveCmd, loadCmd := CreateCommands(source, dest, parentFlags, podman)
switch {
@ -401,15 +394,15 @@ func RemoteArgLength(input string, side int) int {
}
// GetServiceInformation takes the parsed list of hosts to connect to and validates the information
func GetServiceInformation(sshInfo *entities.ImageScpConnections, cliConnections []string, cfg *config.Config) (map[string]config.Destination, error) {
var serv map[string]config.Destination
func GetServiceInformation(sshInfo *entities.ImageScpConnections, cliConnections []string, cfg *config.Config) error {
var urlS string
var iden string
for i, val := range cliConnections {
connection, _, _ := strings.Cut(val, "::")
sshInfo.Connections = append(sshInfo.Connections, connection)
conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]]
if found {
conn, err := cfg.GetConnection(sshInfo.Connections[i], false)
if err == nil {
// connection found
urlS = conn.URI
iden = conn.Identity
} else { // no match, warn user and do a manual connection.
@ -419,17 +412,17 @@ func GetServiceInformation(sshInfo *entities.ImageScpConnections, cliConnections
}
urlFinal, err := url.Parse(urlS) // create an actual url to pass to exec command
if err != nil {
return nil, err
return err
}
if urlFinal.User.Username() == "" {
if urlFinal.User, err = GetUserInfo(urlFinal); err != nil {
return nil, err
return err
}
}
sshInfo.URI = append(sshInfo.URI, urlFinal)
sshInfo.Identities = append(sshInfo.Identities, iden)
}
return serv, nil
return nil
}
func GetUserInfo(uri *url.URL) (*url.Userinfo, error) {

View File

@ -32,7 +32,7 @@ type Schedule struct {
platformBuilders map[string]string // target->connection
}
func newFarmWithBuilders(_ context.Context, name string, destinations *map[string]config.Destination, localEngine entities.ImageEngine, buildLocal bool) (*Farm, error) {
func newFarmWithBuilders(_ context.Context, name string, cons []config.Connection, localEngine entities.ImageEngine, buildLocal bool) (*Farm, error) {
farm := &Farm{
builders: make(map[string]entities.ImageEngine),
localEngine: localEngine,
@ -43,22 +43,22 @@ func newFarmWithBuilders(_ context.Context, name string, destinations *map[strin
builderGroup multierror.Group
)
// Set up the remote connections to handle the builds
for name, dest := range *destinations {
name, dest := name, dest
for _, con := range cons {
con := con
builderGroup.Go(func() error {
fmt.Printf("Connecting to %q\n", name)
fmt.Printf("Connecting to %q\n", con.Name)
engine, err := infra.NewImageEngine(&entities.PodmanConfig{
EngineMode: entities.TunnelMode,
URI: dest.URI,
Identity: dest.Identity,
MachineMode: dest.IsMachine,
FarmNodeName: name,
URI: con.URI,
Identity: con.Identity,
MachineMode: con.IsMachine,
FarmNodeName: con.Name,
})
if err != nil {
return fmt.Errorf("initializing image engine at %q: %w", dest.URI, err)
return fmt.Errorf("initializing image engine at %q: %w", con.URI, err)
}
defer fmt.Printf("Builder %q ready\n", name)
defer fmt.Printf("Builder %q ready\n", con.Name)
builderMutex.Lock()
defer builderMutex.Unlock()
farm.builders[name] = engine
@ -90,12 +90,12 @@ func newFarmWithBuilders(_ context.Context, name string, destinations *map[strin
func NewFarm(ctx context.Context, name string, localEngine entities.ImageEngine, buildLocal bool) (*Farm, error) {
// Get the destinations of the connections specified in the farm
destinations, err := getFarmDestinations(name)
name, destinations, err := getFarmDestinations(name)
if err != nil {
return nil, err
}
return newFarmWithBuilders(ctx, name, &destinations, localEngine, buildLocal)
return newFarmWithBuilders(ctx, name, destinations, localEngine, buildLocal)
}
// Done performs any necessary end-of-process cleanup for the farm's members.
@ -460,22 +460,21 @@ func (f *Farm) Build(ctx context.Context, schedule Schedule, options entities.Bu
return nil
}
func getFarmDestinations(name string) (map[string]config.Destination, error) {
dest := make(map[string]config.Destination)
cfg, err := config.ReadCustomConfig()
func getFarmDestinations(name string) (string, []config.Connection, error) {
cfg, err := config.Default()
if err != nil {
return dest, err
return "", nil, err
}
// If no farm name is given, then grab all the service destinations available
if name == "" {
return cfg.Engine.ServiceDestinations, nil
if name, cons, err := cfg.GetDefaultFarmConnections(); err == nil {
// Use default farm if is there is one
return name, cons, nil
}
// If no farm name is given, then grab all the service destinations available
cons, err := cfg.GetAllConnections()
return name, cons, err
}
// Go through the connections in the farm and get their destination
for _, c := range cfg.Farms.List[name] {
dest[c] = cfg.Engine.ServiceDestinations[c]
}
return dest, nil
cons, err := cfg.GetFarmConnections(name)
return name, cons, err
}

View File

@ -17,93 +17,81 @@ func AddConnection(uri fmt.Stringer, name, identity string, isDefault bool) erro
if len(identity) < 1 {
return errors.New("identity must be defined")
}
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
if _, ok := cfg.Engine.ServiceDestinations[name]; ok {
return errors.New("cannot overwrite connection")
}
if isDefault {
cfg.Engine.ActiveService = name
}
dst := config.Destination{
URI: uri.String(),
IsMachine: true,
}
dst.Identity = identity
if cfg.Engine.ServiceDestinations == nil {
cfg.Engine.ServiceDestinations = map[string]config.Destination{
name: dst,
}
cfg.Engine.ActiveService = name
} else {
cfg.Engine.ServiceDestinations[name] = dst
}
return cfg.Write()
}
func AnyConnectionDefault(name ...string) (bool, error) {
cfg, err := config.ReadCustomConfig()
if err != nil {
return false, err
}
for _, n := range name {
if n == cfg.Engine.ActiveService {
return true, nil
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if _, ok := cfg.Connection.Connections[name]; ok {
return errors.New("cannot overwrite connection")
}
}
return false, nil
dst := config.Destination{
URI: uri.String(),
IsMachine: true,
Identity: identity,
}
if isDefault {
cfg.Connection.Default = name
}
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
name: dst,
}
cfg.Connection.Default = name
} else {
cfg.Connection.Connections[name] = dst
}
return nil
})
}
func ChangeConnectionURI(name string, uri fmt.Stringer) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
dst, ok := cfg.Engine.ServiceDestinations[name]
if !ok {
return errors.New("connection not found")
}
dst.URI = uri.String()
cfg.Engine.ServiceDestinations[name] = dst
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
dst, ok := cfg.Connection.Connections[name]
if !ok {
return errors.New("connection not found")
}
dst.URI = uri.String()
cfg.Connection.Connections[name] = dst
return cfg.Write()
return nil
})
}
func ChangeDefault(name string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
cfg.Engine.ActiveService = name
return cfg.Write()
// UpdateConnectionIfDefault updates the default connection to the rootful/rootless when depending
// on the bool but only if other rootful/less connection was already the default.
// Returns true if it modified the default
func UpdateConnectionIfDefault(rootful bool, name, rootfulName string) error {
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if name == cfg.Connection.Default && rootful {
cfg.Connection.Default = rootfulName
} else if rootfulName == cfg.Connection.Default && !rootful {
cfg.Connection.Default = name
}
return nil
})
}
func RemoveConnections(names ...string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
for _, name := range names {
if _, ok := cfg.Engine.ServiceDestinations[name]; ok {
delete(cfg.Engine.ServiceDestinations, name)
} else {
return fmt.Errorf("unable to find connection named %q", name)
}
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
for _, name := range names {
if _, ok := cfg.Connection.Connections[name]; ok {
delete(cfg.Connection.Connections, name)
} else {
return fmt.Errorf("unable to find connection named %q", name)
}
if cfg.Engine.ActiveService == name {
cfg.Engine.ActiveService = ""
for service := range cfg.Engine.ServiceDestinations {
cfg.Engine.ActiveService = service
break
if cfg.Connection.Default == name {
cfg.Connection.Default = ""
}
}
}
return cfg.Write()
for service := range cfg.Connection.Connections {
cfg.Connection.Default = service
break
}
return nil
})
}
// removeFilesAndConnections removes any files and connections with the given names

View File

@ -158,22 +158,7 @@ following command in your terminal session:
// SetRootful modifies the machine's default connection to be either rootful or
// rootless
func SetRootful(rootful bool, name, rootfulName string) error {
changeCon, err := AnyConnectionDefault(name, rootfulName)
if err != nil {
return err
}
if changeCon {
newDefault := name
if rootful {
newDefault += "-root"
}
err := ChangeDefault(newDefault)
if err != nil {
return err
}
}
return nil
return UpdateConnectionIfDefault(rootful, name, rootfulName)
}
// WriteConfig writes the machine's JSON config file

View File

@ -122,6 +122,7 @@ var (
// invalid containers.conf files to fail the cleanup.
os.Unsetenv("CONTAINERS_CONF")
os.Unsetenv("CONTAINERS_CONF_OVERRIDE")
os.Unsetenv("PODMAN_CONNECTIONS_CONF")
podmanTest.Cleanup()
f := CurrentSpecReport()
processTestResult(f)

View File

@ -4,7 +4,6 @@ import (
"os"
"path/filepath"
"github.com/containers/common/pkg/config"
. "github.com/containers/podman/v4/test/utils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -13,21 +12,20 @@ import (
func setupContainersConfWithSystemConnections() {
// make sure connections are not written to real user config on host
file := filepath.Join(podmanTest.TempDir, "containersconf")
file := filepath.Join(podmanTest.TempDir, "containers.conf")
f, err := os.Create(file)
Expect(err).ToNot(HaveOccurred())
connections := `
[engine]
active_service = "QA"
[engine.service_destinations]
[engine.service_destinations.QA]
uri = "ssh://root@podman.test:2222/run/podman/podman.sock"
[engine.service_destinations.QB]
uri = "ssh://root@podman.test:3333/run/podman/podman.sock"`
_, err = f.WriteString(connections)
Expect(err).ToNot(HaveOccurred())
f.Close()
os.Setenv("CONTAINERS_CONF", file)
file = filepath.Join(podmanTest.TempDir, "connections.conf")
f, err = os.Create(file)
Expect(err).ToNot(HaveOccurred())
connections := `{"connection":{"default":"QA","connections":{"QA":{"uri":"ssh://root@podman.test:2222/run/podman/podman.sock"},"QB":{"uri":"ssh://root@podman.test:3333/run/podman/podman.sock"}}}}`
_, err = f.WriteString(connections)
Expect(err).ToNot(HaveOccurred())
f.Close()
os.Setenv("PODMAN_CONNECTIONS_CONF", file)
}
var _ = Describe("podman farm", func() {
@ -36,19 +34,12 @@ var _ = Describe("podman farm", func() {
Context("without running API service", func() {
It("verify system connections exist", func() {
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg).Should(HaveActiveService("QA"))
Expect(cfg).Should(VerifyService(
"QA",
"ssh://root@podman.test:2222/run/podman/podman.sock",
"",
))
Expect(cfg).Should(VerifyService(
"QB",
"ssh://root@podman.test:3333/run/podman/podman.sock",
"",
))
session := podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`QA ssh://root@podman.test:2222/run/podman/podman.sock true true
QB ssh://root@podman.test:3333/run/podman/podman.sock false true
`))
})
It("create farm", func() {
@ -73,12 +64,13 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm3\" created"))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm3", []string{}))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`farm1 [QA QB] true true
farm2 [QA] false true
farm3 [] false true
`))
// create existing farm should exit with error
cmd = []string{"farm", "create", "farm3"}
@ -109,12 +101,13 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm3\" created"))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm3", []string{}))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`farm1 [QA QB] true true
farm2 [QA] false true
farm3 [] false true
`))
// update farm1 to remove the QA connection from it
cmd = []string{"farm", "update", "--remove", "QA,QB", "farm1"}
@ -137,12 +130,13 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm2\" updated"))
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm2"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm3", []string{"QB"}))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`farm1 [] false true
farm2 [QA] true true
farm3 [QB] false true
`))
// update farm2 to not be the default, no farms should be the default
cmd = []string{"farm", "update", "--default=false", "farm2"}
@ -151,9 +145,13 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm2\" updated"))
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(BeEmpty())
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`farm1 [] false true
farm2 [QA] false true
farm3 [QB] false true
`))
})
It("update farm with non-existing connections", func() {
@ -171,11 +169,12 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm2\" created"))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`farm1 [QA QB] true true
farm2 [QA] false true
`))
// update farm1 to add no-node connection to it
cmd = []string{"farm", "update", "--add", "no-node", "farm1"}
@ -189,12 +188,13 @@ var _ = Describe("podman farm", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitWithError())
// read config again to ensure that nothing has changed
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
// check again to ensure that nothing has changed
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`farm1 [QA QB] true true
farm2 [QA] false true
`))
})
It("update non-existent farm", func() {
@ -205,11 +205,6 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm1\" created"))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
// update non-existent farm to add QA connection to it
cmd = []string{"farm", "update", "--add", "no-node", "non-existent"}
session = podmanTest.Podman(cmd)
@ -222,11 +217,10 @@ var _ = Describe("podman farm", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitWithError())
// read config again and ensure nothing has changed
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal(`farm1 [QA QB] true true`))
})
It("remove farms", func() {
@ -244,11 +238,12 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm2\" created"))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`farm1 [QA QB] true true
farm2 [QA] false true
`))
// remove farm1 and a non-existent farm
// farm 1 should be removed and a warning printed for nonexistent-farm
@ -259,11 +254,10 @@ var _ = Describe("podman farm", func() {
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm1\" deleted"))
Expect(session.ErrorToString()).Should(ContainSubstring("doesn't exist; nothing to remove"))
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm2"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
Expect(cfg.Farms.List).Should(Not(HaveKey("farm1")))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal(`farm2 [QA] true true`))
// remove all non-existent farms and expect an error
cmd = []string{"farm", "rm", "foo", "bar"}
@ -287,12 +281,6 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"farm2\" created"))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(Equal("farm1"))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA", "QB"}))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm2", []string{"QA"}))
// remove --all
cmd = []string{"farm", "rm", "--all"}
session = podmanTest.Podman(cmd)
@ -300,10 +288,10 @@ var _ = Describe("podman farm", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("All farms have been deleted"))
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.Default).Should(BeEmpty())
Expect(cfg.Farms.List).Should(BeEmpty())
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal(""))
})
})
})

View File

@ -4,7 +4,6 @@ import (
"os"
"path/filepath"
"github.com/containers/common/pkg/config"
. "github.com/containers/podman/v4/test/utils"
"github.com/containers/storage/pkg/homedir"
. "github.com/onsi/ginkgo/v2"
@ -13,7 +12,7 @@ import (
var _ = Describe("podman image scp", func() {
BeforeEach(setupEmptyContainersConf)
BeforeEach(setupConnectionsConf)
It("podman image scp bogus image", func() {
scp := podmanTest.Podman([]string{"image", "scp", "FOOBAR"})
@ -34,15 +33,6 @@ var _ = Describe("podman image scp", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Engine).Should(HaveField("ActiveService", "QA"))
Expect(cfg.Engine.ServiceDestinations).To(HaveKeyWithValue("QA",
config.Destination{
URI: "ssh://root@podman.test:2222/run/podman/podman.sock",
},
))
scp := podmanTest.Podman([]string{"image", "scp", ALPINE, "QA::"})
scp.WaitWithDefaultTimeout()
// exit with error because we cannot make an actual ssh connection

View File

@ -8,7 +8,6 @@ import (
"os/user"
"path/filepath"
"github.com/containers/common/pkg/config"
. "github.com/containers/podman/v4/test/utils"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -16,18 +15,24 @@ import (
. "github.com/onsi/gomega/gexec"
)
func setupEmptyContainersConf() {
func setupConnectionsConf() {
// make sure connections are not written to real user config on host
file := filepath.Join(podmanTest.TempDir, "containersconf")
file := filepath.Join(podmanTest.TempDir, "containers.conf")
f, err := os.Create(file)
Expect(err).ToNot(HaveOccurred())
f.Close()
os.Setenv("CONTAINERS_CONF", file)
file = filepath.Join(podmanTest.TempDir, "connections.conf")
os.Setenv("PODMAN_CONNECTIONS_CONF", file)
}
var systemConnectionListCmd = []string{"system", "connection", "ls", "--format", "{{.Name}} {{.URI}} {{.Identity}} {{.Default}} {{.ReadWrite}}"}
var farmListCmd = []string{"farm", "ls", "--format", "{{.Name}} {{.Connections}} {{.Default}} {{.ReadWrite}}"}
var _ = Describe("podman system connection", func() {
BeforeEach(setupEmptyContainersConf)
BeforeEach(setupConnectionsConf)
Context("without running API service", func() {
It("add ssh://", func() {
@ -42,14 +47,10 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg).Should(HaveActiveService("QA"))
Expect(cfg).Should(VerifyService(
"QA",
"ssh://root@podman.test:2222/run/podman/podman.sock",
"~/.ssh/id_rsa",
))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("QA ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa true true"))
cmd = []string{"system", "connection", "rename",
"QA",
@ -59,7 +60,10 @@ var _ = Describe("podman system connection", func() {
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(config.ReadCustomConfig()).Should(HaveActiveService("QE"))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("QE ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa true true"))
})
It("add UDS", func() {
@ -74,11 +78,10 @@ var _ = Describe("podman system connection", func() {
// stderr will probably warn (ENOENT or EACCESS) about socket
// but it's too unreliable to test for.
Expect(config.ReadCustomConfig()).Should(VerifyService(
"QA-UDS",
"unix:///run/podman/podman.sock",
"",
))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("QA-UDS unix:///run/podman/podman.sock true true"))
cmd = []string{"system", "connection", "add",
"QA-UDS1",
@ -90,12 +93,12 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(Exit(0))
Expect(session.Out.Contents()).Should(BeEmpty())
Expect(config.ReadCustomConfig()).Should(HaveActiveService("QA-UDS"))
Expect(config.ReadCustomConfig()).Should(VerifyService(
"QA-UDS1",
"unix:///run/user/podman/podman.sock",
"",
))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`QA-UDS unix:///run/podman/podman.sock true true
QA-UDS1 unix:///run/user/podman/podman.sock false true
`))
})
It("add tcp", func() {
@ -108,18 +111,13 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
Expect(config.ReadCustomConfig()).Should(VerifyService(
"QA-TCP",
"tcp://localhost:8888",
"",
))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("QA-TCP tcp://localhost:8888 true true"))
})
It("add to new farm", func() {
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.List).Should(BeEmpty())
cmd := []string{"system", "connection", "add",
"--default",
"--identity", "~/.ssh/id_rsa",
@ -132,15 +130,14 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg).Should(HaveActiveService("QA"))
Expect(cfg).Should(VerifyService(
"QA",
"ssh://root@podman.test:2222/run/podman/podman.sock",
"~/.ssh/id_rsa",
))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA"}))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("QA ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa true true"))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("farm1 [QA] true true"))
})
It("add to existing farm", func() {
@ -151,10 +148,6 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(ContainSubstring("Farm \"empty-farm\" created"))
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.List).Should(HaveKeyWithValue("empty-farm", []string{}))
cmd = []string{"system", "connection", "add",
"--default",
"--identity", "~/.ssh/id_rsa",
@ -167,22 +160,17 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg).Should(HaveActiveService("QA"))
Expect(cfg).Should(VerifyService(
"QA",
"ssh://root@podman.test:2222/run/podman/podman.sock",
"~/.ssh/id_rsa",
))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("empty-farm", []string{"QA"}))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("QA ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa true true"))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("empty-farm [QA] true true"))
})
It("removing connection should remove from farm also", func() {
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Farms.List).Should(BeEmpty())
cmd := []string{"system", "connection", "add",
"--default",
"--identity", "~/.ssh/id_rsa",
@ -195,15 +183,14 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg).Should(HaveActiveService("QA"))
Expect(cfg).Should(VerifyService(
"QA",
"ssh://root@podman.test:2222/run/podman/podman.sock",
"~/.ssh/id_rsa",
))
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{"QA"}))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("QA ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa true true"))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("farm1 [QA] true true"))
// Remove the QA connection
session = podmanTest.Podman([]string{"system", "connection", "remove", "QA"})
@ -211,11 +198,14 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
cfg, err = config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Engine.ActiveService).Should(BeEmpty())
Expect(cfg.Engine.ServiceDestinations).Should(BeEmpty())
Expect(cfg.Farms.List).Should(HaveKeyWithValue("farm1", []string{}))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal(""))
session = podmanTest.Podman(farmListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("farm1 [] true true"))
})
It("remove", func() {
@ -235,10 +225,10 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
cfg, err := config.ReadCustomConfig()
Expect(err).ShouldNot(HaveOccurred())
Expect(cfg.Engine.ActiveService).Should(BeEmpty())
Expect(cfg.Engine.ServiceDestinations).Should(BeEmpty())
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal(""))
}
})
@ -261,6 +251,7 @@ var _ = Describe("podman system connection", func() {
session = podmanTest.Podman([]string{"system", "connection", "list"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToStringArray()).To(HaveLen(1))
})
It("default", func() {
@ -282,19 +273,18 @@ var _ = Describe("podman system connection", func() {
Expect(session).Should(ExitCleanly())
Expect(session.Out.Contents()).Should(BeEmpty())
Expect(config.ReadCustomConfig()).Should(HaveActiveService("devl"))
session = podmanTest.Podman(systemConnectionListCmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(string(session.Out.Contents())).To(Equal(`devl ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa true true
qe ssh://root@podman.test:2222/run/podman/podman.sock ~/.ssh/id_rsa false true
`))
cmd = []string{"system", "connection", "list"}
session = podmanTest.Podman(cmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.Out).Should(Say("Name *URI *Identity *Default"))
cmd = []string{"system", "connection", "list", "--format", "{{.Name}}"}
session = podmanTest.Podman(cmd)
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).Should(Equal("devl qe"))
})
It("failed default", func() {
@ -382,12 +372,12 @@ var _ = Describe("podman system connection", func() {
Path: fmt.Sprintf("/run/user/%s/podman/podman.sock", u.Uid),
}
Expect(config.ReadCustomConfig()).Should(HaveActiveService("QA"))
Expect(config.ReadCustomConfig()).Should(VerifyService(
"QA",
uri.String(),
filepath.Join(u.HomeDir, ".ssh", "id_ed25519"),
))
cmd = exec.Command(podmanTest.RemotePodmanBinary, systemConnectionListCmd...)
lsSession, err := Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
lsSession.Wait(DefaultWaitTimeout)
Expect(lsSession).Should(Exit(0))
Expect(string(lsSession.Out.Contents())).To(Equal("QA " + uri.String() + " " + filepath.Join(u.HomeDir, ".ssh", "id_ed25519") + " true true\n"))
})
})
})

View File

@ -51,7 +51,7 @@ function _run_podman_remote() {
# Very basic test, does not actually connect at any time
@test "podman system connection - basic add / ls / remove" {
run_podman system connection ls
is "$output" "Name URI Identity Default" \
is "$output" "Name URI Identity Default ReadWrite" \
"system connection ls: no connections"
c1="c1_$(random_string 15)"
@ -61,8 +61,8 @@ function _run_podman_remote() {
run_podman context create --docker "host=tcp://localhost:54321" $c2
run_podman system connection ls
is "$output" \
".*$c1[ ]\+tcp://localhost:12345[ ]\+true
$c2[ ]\+tcp://localhost:54321[ ]\+false" \
".*$c1[ ]\+tcp://localhost:12345[ ]\+true[ ]\+true
$c2[ ]\+tcp://localhost:54321[ ]\+false[ ]\+true" \
"system connection ls"
run_podman system connection ls -q
is "$(echo $(sort <<<$output))" \
@ -75,14 +75,14 @@ $c2[ ]\+tcp://localhost:54321[ ]\+false" \
run_podman context use $c2
run_podman system connection ls
is "$output" \
".*$c1[ ]\+tcp://localhost:12345[ ]\+false
$c2[ ]\+tcp://localhost:54321[ ]\+true" \
".*$c1[ ]\+tcp://localhost:12345[ ]\+false[ ]\+true
$c2[ ]\+tcp://localhost:54321[ ]\+true[ ]\+true" \
"system connection ls"
# Remove default connection; the remaining one should still not be default
run_podman system connection rm $c2
run_podman context ls
is "$output" ".*$c1[ ]\+tcp://localhost:12345[ ]\+false" \
is "$output" ".*$c1[ ]\+tcp://localhost:12345[ ]\+false[ ]\+true" \
"system connection ls (after removing default connection)"
run_podman context rm $c1

View File

@ -3,116 +3,12 @@ package utils
import (
"encoding/json"
"fmt"
"net/url"
"github.com/containers/common/pkg/config"
. "github.com/onsi/gomega" //nolint:revive,stylecheck
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/gexec"
"github.com/onsi/gomega/matchers"
"github.com/onsi/gomega/types"
)
// HaveActiveService verifies the given service is the active service.
func HaveActiveService(name interface{}) OmegaMatcher {
return WithTransform(
func(cfg *config.Config) string {
return cfg.Engine.ActiveService
},
Equal(name))
}
type ServiceMatcher struct {
types.GomegaMatcher
Name interface{}
URI interface{}
Identity interface{}
failureMessage string
negatedFailureMessage string
}
func VerifyService(name, uri, identity interface{}) OmegaMatcher {
return &ServiceMatcher{
Name: name,
URI: uri,
Identity: identity,
}
}
func (matcher *ServiceMatcher) Match(actual interface{}) (success bool, err error) {
cfg, ok := actual.(*config.Config)
if !ok {
return false, fmt.Errorf("ServiceMatcher matcher expects a config.Config")
}
if _, err = url.Parse(matcher.URI.(string)); err != nil {
return false, err
}
success, err = HaveKey(matcher.Name).Match(cfg.Engine.ServiceDestinations)
if !success || err != nil {
matcher.failureMessage = HaveKey(matcher.Name).FailureMessage(cfg.Engine.ServiceDestinations)
matcher.negatedFailureMessage = HaveKey(matcher.Name).NegatedFailureMessage(cfg.Engine.ServiceDestinations)
return
}
sd := cfg.Engine.ServiceDestinations[matcher.Name.(string)]
success, err = Equal(matcher.URI).Match(sd.URI)
if !success || err != nil {
matcher.failureMessage = Equal(matcher.URI).FailureMessage(sd.URI)
matcher.negatedFailureMessage = Equal(matcher.URI).NegatedFailureMessage(sd.URI)
return
}
success, err = Equal(matcher.Identity).Match(sd.Identity)
if !success || err != nil {
matcher.failureMessage = Equal(matcher.Identity).FailureMessage(sd.Identity)
matcher.negatedFailureMessage = Equal(matcher.Identity).NegatedFailureMessage(sd.Identity)
return
}
return true, nil
}
func (matcher *ServiceMatcher) FailureMessage(_ interface{}) string {
return matcher.failureMessage
}
func (matcher *ServiceMatcher) NegatedFailureMessage(_ interface{}) string {
return matcher.negatedFailureMessage
}
type URLMatcher struct {
matchers.EqualMatcher
}
// VerifyURL matches when actual is a valid URL and matches expected.
func VerifyURL(uri interface{}) OmegaMatcher {
return &URLMatcher{matchers.EqualMatcher{Expected: uri}}
}
func (matcher *URLMatcher) Match(actual interface{}) (bool, error) {
e, ok := matcher.Expected.(string)
if !ok {
return false, fmt.Errorf("VerifyURL requires string inputs %T is not supported", matcher.Expected)
}
eURI, err := url.Parse(e)
if err != nil {
return false, err
}
a, ok := actual.(string)
if !ok {
return false, fmt.Errorf("VerifyURL requires string inputs %T is not supported", actual)
}
aURI, err := url.Parse(a)
if err != nil {
return false, err
}
return (&matchers.EqualMatcher{Expected: eURI}).Match(aURI)
}
type ExitMatcher struct {
types.GomegaMatcher
Expected int

View File

@ -9,11 +9,9 @@ import (
"runtime"
"strings"
"github.com/BurntSushi/toml"
"github.com/containers/common/internal/attributedstring"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/capabilities"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/unshare"
units "github.com/docker/go-units"
selinux "github.com/opencontainers/selinux/go-selinux"
@ -667,9 +665,9 @@ type MachineConfig struct {
// FarmConfig represents the "farm" TOML config tables
type FarmConfig struct {
// Default is the default farm to be used when farming out builds
Default string `toml:"default,omitempty"`
Default string `json:",omitempty" toml:"default,omitempty"`
// List is a map of farms created where key=farm-name and value=list of connections
List map[string][]string `toml:"list,omitempty"`
List map[string][]string `json:",omitempty" toml:"list,omitempty"`
}
// Destination represents destination for remote service
@ -678,10 +676,10 @@ type Destination struct {
URI string `toml:"uri"`
// Identity file with ssh key, optional
Identity string `toml:"identity,omitempty"`
Identity string `json:",omitempty" toml:"identity,omitempty"`
// isMachine describes if the remote destination is a machine.
IsMachine bool `toml:"is_machine,omitempty"`
IsMachine bool `json:",omitempty" toml:"is_machine,omitempty"`
}
// Consumes container image's os and arch and returns if any dedicated runtime was
@ -1008,82 +1006,6 @@ func IsValidDeviceMode(mode string) bool {
return true
}
func rootlessConfigPath() (string, error) {
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
return filepath.Join(configHome, _configPath), nil
}
home, err := unshare.HomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, UserOverrideContainersConfig), nil
}
func Path() string {
if path := os.Getenv("CONTAINERS_CONF"); path != "" {
return path
}
if unshare.IsRootless() {
if rpath, err := rootlessConfigPath(); err == nil {
return rpath
}
return "$HOME/" + UserOverrideContainersConfig
}
return OverrideContainersConfig
}
// ReadCustomConfig reads the custom config and only generates a config based on it
// If the custom config file does not exists, function will return an empty config
func ReadCustomConfig() (*Config, error) {
path, err := customConfigFile()
if err != nil {
return nil, err
}
newConfig := &Config{}
if _, err := os.Stat(path); err == nil {
if err := readConfigFromFile(path, newConfig); err != nil {
return nil, err
}
} else {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
}
// Let's always initialize the farm list so it is never nil
if newConfig.Farms.List == nil {
newConfig.Farms.List = make(map[string][]string)
}
return newConfig, nil
}
// Write writes the configuration to the default file
func (c *Config) Write() error {
var err error
path, err := customConfigFile()
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
opts := &ioutils.AtomicFileWriterOptions{ExplicitCommit: true}
configFile, err := ioutils.NewAtomicFileWriterWithOpts(path, 0o644, opts)
if err != nil {
return err
}
defer configFile.Close()
enc := toml.NewEncoder(configFile)
if err := enc.Encode(c); err != nil {
return err
}
// If no errors commit the changes to the config file
return configFile.Commit()
}
// Reload clean the cached config and reloads the configuration from containers.conf files
// This function is meant to be used for long-running processes that need to reload potential changes made to
// the cached containers.conf files.

View File

@ -1,9 +1,5 @@
package config
import (
"os"
)
const (
// OverrideContainersConfig holds the default config path overridden by the root user
OverrideContainersConfig = "/etc/" + _configPath
@ -16,18 +12,6 @@ const (
DefaultSignaturePolicyPath = "/etc/containers/policy.json"
)
// podman remote clients on darwin cannot use unshare.isRootless() to determine the configuration file locations.
func customConfigFile() (string, error) {
if path, found := os.LookupEnv("CONTAINERS_CONF"); found {
return path, nil
}
return rootlessConfigPath()
}
func ifRootlessConfigPath() (string, error) {
return rootlessConfigPath()
}
var defaultHelperBinariesDir = []string{
// Relative to the binary directory
"$BINDIR/../libexec/podman",

View File

@ -1,9 +1,5 @@
package config
import (
"os"
)
const (
// OverrideContainersConfig holds the default config path overridden by the root user
OverrideContainersConfig = "/usr/local/etc/" + _configPath
@ -16,18 +12,6 @@ const (
DefaultSignaturePolicyPath = "/usr/local/etc/containers/policy.json"
)
// podman remote clients on freebsd cannot use unshare.isRootless() to determine the configuration file locations.
func customConfigFile() (string, error) {
if path, found := os.LookupEnv("CONTAINERS_CONF"); found {
return path, nil
}
return rootlessConfigPath()
}
func ifRootlessConfigPath() (string, error) {
return rootlessConfigPath()
}
var defaultHelperBinariesDir = []string{
"/usr/local/bin",
"/usr/local/libexec/podman",

View File

@ -1,9 +1,6 @@
package config
import (
"os"
"github.com/containers/storage/pkg/unshare"
selinux "github.com/opencontainers/selinux/go-selinux"
)
@ -23,31 +20,6 @@ func selinuxEnabled() bool {
return selinux.GetEnabled()
}
func customConfigFile() (string, error) {
if path, found := os.LookupEnv("CONTAINERS_CONF"); found {
return path, nil
}
if unshare.GetRootlessUID() > 0 {
path, err := rootlessConfigPath()
if err != nil {
return "", err
}
return path, nil
}
return OverrideContainersConfig, nil
}
func ifRootlessConfigPath() (string, error) {
if unshare.GetRootlessUID() > 0 {
path, err := rootlessConfigPath()
if err != nil {
return "", err
}
return path, nil
}
return "", nil
}
var defaultHelperBinariesDir = []string{
"/usr/local/libexec/podman",
"/usr/local/lib/podman",

View File

@ -0,0 +1,25 @@
//go:build !windows
package config
import (
"os"
"path/filepath"
"github.com/containers/storage/pkg/unshare"
)
// userConfigPath returns the path to the users local config that is
// not shared with other users. It uses $XDG_CONFIG_HOME/containers...
// if set or $HOME/.config/containers... if not.
func userConfigPath() (string, error) {
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
return filepath.Join(configHome, _configPath), nil
}
home, err := unshare.HomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, UserOverrideContainersConfig), nil
}

View File

@ -17,15 +17,9 @@ const (
_typeBind = "bind"
)
// podman remote clients on windows cannot use unshare.isRootless() to determine the configuration file locations.
func customConfigFile() (string, error) {
if path, found := os.LookupEnv("CONTAINERS_CONF"); found {
return path, nil
}
return os.Getenv("APPDATA") + "\\containers\\containers.conf", nil
}
func ifRootlessConfigPath() (string, error) {
// userConfigPath returns the path to the users local config that is
// not shared with other users. It uses $APPDATA/containers...
func userConfigPath() (string, error) {
return os.Getenv("APPDATA") + "\\containers\\containers.conf", nil
}

View File

@ -0,0 +1,286 @@
package config
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/containers/storage/pkg/ioutils"
)
const connectionsFile = "podman-connections.conf"
// connectionsConfigFile returns the path to the rw connections config file
func connectionsConfigFile() (string, error) {
if path, found := os.LookupEnv("PODMAN_CONNECTIONS_CONF"); found {
return path, nil
}
path, err := userConfigPath()
if err != nil {
return "", err
}
// file is stored next to containers.conf
return filepath.Join(filepath.Dir(path), connectionsFile), nil
}
type ConnectionConfig struct {
Default string `json:",omitempty"`
Connections map[string]Destination `json:",omitempty"`
}
type ConnectionsFile struct {
Connection ConnectionConfig `json:",omitempty"`
Farm FarmConfig `json:",omitempty"`
}
type Connection struct {
// Name of the connection
Name string
// Destination for this connection
Destination
// Default if this connection is the default
Default bool
// ReadWrite if true the connection is stored in the connections file
ReadWrite bool
}
type Farm struct {
// Name of the farm
Name string
// Connections
Connections []string
// Default if this is the default farm
Default bool
// ReadWrite if true the farm is stored in the connections file
ReadWrite bool
}
func readConnectionConf() (*ConnectionsFile, string, error) {
path, err := connectionsConfigFile()
if err != nil {
return nil, "", err
}
conf := new(ConnectionsFile)
f, err := os.Open(path)
if err != nil {
// return empty config if file does not exists
if errors.Is(err, fs.ErrNotExist) {
return conf, path, nil
}
return nil, "", err
}
defer f.Close()
err = json.NewDecoder(f).Decode(conf)
if err != nil {
return nil, "", fmt.Errorf("parse %q: %w", path, err)
}
return conf, path, nil
}
func writeConnectionConf(path string, conf *ConnectionsFile) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
opts := &ioutils.AtomicFileWriterOptions{ExplicitCommit: true}
configFile, err := ioutils.NewAtomicFileWriterWithOpts(path, 0o644, opts)
if err != nil {
return err
}
defer configFile.Close()
err = json.NewEncoder(configFile).Encode(conf)
if err != nil {
return err
}
// If no errors commit the changes to the config file
return configFile.Commit()
}
// EditConnectionConfig must be used to edit the connections config.
// The function will read and write the file automatically and the
// callback function just needs to modify the cfg as needed.
func EditConnectionConfig(callback func(cfg *ConnectionsFile) error) error {
conf, path, err := readConnectionConf()
if err != nil {
return fmt.Errorf("read connections file: %w", err)
}
if conf.Farm.List == nil {
conf.Farm.List = make(map[string][]string)
}
if err := callback(conf); err != nil {
return err
}
return writeConnectionConf(path, conf)
}
func makeConnection(name string, dst Destination, def, readWrite bool) *Connection {
return &Connection{
Name: name,
Destination: dst,
Default: def,
ReadWrite: readWrite,
}
}
// GetConnection return the connection for the given name or if def is set to true then return the default connection.
func (c *Config) GetConnection(name string, def bool) (*Connection, error) {
conConf, _, err := readConnectionConf()
if err != nil {
return nil, err
}
defaultCon := conConf.Connection.Default
if defaultCon == "" {
defaultCon = c.Engine.ActiveService
}
if def {
if defaultCon == "" {
return nil, errors.New("no default connection found")
}
name = defaultCon
} else {
def = defaultCon == name
}
if dst, ok := conConf.Connection.Connections[name]; ok {
return makeConnection(name, dst, def, true), nil
}
if dst, ok := c.Engine.ServiceDestinations[name]; ok {
return makeConnection(name, dst, def, false), nil
}
return nil, fmt.Errorf("connection %q not found", name)
}
// GetAllConnections return all configured connections
func (c *Config) GetAllConnections() ([]Connection, error) {
conConf, _, err := readConnectionConf()
if err != nil {
return nil, err
}
defaultCon := conConf.Connection.Default
if defaultCon == "" {
defaultCon = c.Engine.ActiveService
}
connections := make([]Connection, 0, len(conConf.Connection.Connections))
for name, dst := range conConf.Connection.Connections {
def := defaultCon == name
connections = append(connections, *makeConnection(name, dst, def, true))
}
for name, dst := range c.Engine.ServiceDestinations {
if _, ok := conConf.Connection.Connections[name]; ok {
// connection name is overwritten by connections file
continue
}
def := defaultCon == name
connections = append(connections, *makeConnection(name, dst, def, false))
}
return connections, nil
}
func getConnections(cons []string, dests map[string]Destination) ([]Connection, error) {
connections := make([]Connection, 0, len(cons))
for _, name := range cons {
if dst, ok := dests[name]; ok {
connections = append(connections, *makeConnection(name, dst, false, false))
} else {
return nil, fmt.Errorf("connection %q not found", name)
}
}
return connections, nil
}
// GetFarmConnections return all the connections for the given farm.
func (c *Config) GetFarmConnections(name string) ([]Connection, error) {
_, cons, err := c.getFarmConnections(name, false)
return cons, err
}
// GetDefaultFarmConnections returns the name of the default farm
// and the connections.
func (c *Config) GetDefaultFarmConnections() (string, []Connection, error) {
return c.getFarmConnections("", true)
}
// getFarmConnections returns all connections for the given farm,
// if def is true it will use the default farm instead of the name.
// Returns the name of the farm and the connections for it.
func (c *Config) getFarmConnections(name string, def bool) (string, []Connection, error) {
conConf, _, err := readConnectionConf()
if err != nil {
return "", nil, err
}
defaultFarm := conConf.Farm.Default
if defaultFarm == "" {
defaultFarm = c.Farms.Default
}
if def {
if defaultFarm == "" {
return "", nil, errors.New("no default farm found")
}
name = defaultFarm
}
if cons, ok := conConf.Farm.List[name]; ok {
cons, err := getConnections(cons, conConf.Connection.Connections)
return name, cons, err
}
if cons, ok := c.Farms.List[name]; ok {
cons, err := getConnections(cons, c.Engine.ServiceDestinations)
return name, cons, err
}
return "", nil, fmt.Errorf("farm %q not found", name)
}
func makeFarm(name string, cons []string, def, readWrite bool) Farm {
return Farm{
Name: name,
Connections: cons,
Default: def,
ReadWrite: readWrite,
}
}
// GetAllFarms returns all configured farms
func (c *Config) GetAllFarms() ([]Farm, error) {
conConf, _, err := readConnectionConf()
if err != nil {
return nil, err
}
defaultFarm := conConf.Farm.Default
if defaultFarm == "" {
defaultFarm = c.Farms.Default
}
farms := make([]Farm, 0, len(conConf.Farm.List))
for name, cons := range conConf.Farm.List {
def := defaultFarm == name
farms = append(farms, makeFarm(name, cons, def, true))
}
for name, cons := range c.Farms.List {
if _, ok := conConf.Farm.List[name]; ok {
// farm name is overwritten by connections file
continue
}
def := defaultFarm == name
farms = append(farms, makeFarm(name, cons, def, false))
}
return farms, nil
}

View File

@ -10,7 +10,8 @@
# locations in the following order:
# 1. /usr/share/containers/containers.conf
# 2. /etc/containers/containers.conf
# 3. $HOME/.config/containers/containers.conf (Rootless containers ONLY)
# 3. $XDG_CONFIG_HOME/containers/containers.conf or
# $HOME/.config/containers/containers.conf if $XDG_CONFIG_HOME is not set
# Items specified in the latter containers.conf, if they exist, override the
# previous containers.conf settings, or the default settings.

View File

@ -21,7 +21,6 @@ var (
)
const (
// FIXME: update code base and tests to use the two constants below.
containersConfEnv = "CONTAINERS_CONF"
containersConfOverrideEnv = containersConfEnv + "_OVERRIDE"
)
@ -79,15 +78,34 @@ func newLocked(options *Options) (*Config, error) {
if err != nil {
return nil, fmt.Errorf("finding config on system: %w", err)
}
// connectionsPath, err := connectionsConfigFile()
// if err != nil {
// return nil, err
// }
for _, path := range configs {
// var dests []*Destination
// if path == connectionsPath {
// // Store the dest pointers so we know after the load if there are new pointers
// // the connection changed and thus is read write.
// dests = maps.Values(config.Engine.ServiceDestinations)
// }
// Merge changes in later configs with the previous configs.
// Each config file that specified fields, will override the
// previous fields.
if err = readConfigFromFile(path, config); err != nil {
if err = readConfigFromFile(path, config, true); err != nil {
return nil, fmt.Errorf("reading system config %q: %w", path, err)
}
logrus.Debugf("Merged system config %q", path)
logrus.Tracef("%+v", config)
// // if there is a new dest now we know it is read write as it was in the connections.conf file
// for _, dest := range config.Engine.ServiceDestinations {
// if !slices.Contains(dests, dest) {
// dest.ReadWrite = true
// }
// }
}
modules, err := options.modules()
@ -115,7 +133,7 @@ func newLocked(options *Options) (*Config, error) {
}
// readConfigFromFile reads in container config in the specified
// file and then merge changes with the current default.
if err := readConfigFromFile(add, config); err != nil {
if err := readConfigFromFile(add, config, false); err != nil {
return nil, fmt.Errorf("reading additional config %q: %w", add, err)
}
logrus.Debugf("Merged additional config %q", add)
@ -157,12 +175,8 @@ func systemConfigs() (configs []string, finalErr error) {
}
return append(configs, path), nil
}
if _, err := os.Stat(DefaultContainersConfig); err == nil {
configs = append(configs, DefaultContainersConfig)
}
if _, err := os.Stat(OverrideContainersConfig); err == nil {
configs = append(configs, OverrideContainersConfig)
}
configs = append(configs, DefaultContainersConfig)
configs = append(configs, OverrideContainersConfig)
var err error
configs, err = addConfigs(OverrideContainersConfig+".d", configs)
@ -170,18 +184,14 @@ func systemConfigs() (configs []string, finalErr error) {
return nil, err
}
path, err := ifRootlessConfigPath()
path, err := userConfigPath()
if err != nil {
return nil, err
}
if path != "" {
if _, err := os.Stat(path); err == nil {
configs = append(configs, path)
}
configs, err = addConfigs(path+".d", configs)
if err != nil {
return nil, err
}
configs = append(configs, path)
configs, err = addConfigs(path+".d", configs)
if err != nil {
return nil, err
}
return configs, nil
}
@ -225,10 +235,13 @@ func addConfigs(dirPath string, configs []string) ([]string, error) {
// unmarshal its content into a Config. The config param specifies the previous
// default config. If the path, only specifies a few fields in the Toml file
// the defaults from the config parameter will be used for all other fields.
func readConfigFromFile(path string, config *Config) error {
func readConfigFromFile(path string, config *Config, ignoreErrNotExist bool) error {
logrus.Tracef("Reading configuration file %q", path)
meta, err := toml.DecodeFile(path, config)
if err != nil {
if ignoreErrNotExist && errors.Is(err, fs.ErrNotExist) {
return nil
}
return fmt.Errorf("decode configuration %v: %w", path, err)
}
keys := meta.Undecoded()

View File

@ -19,6 +19,7 @@ type List interface {
Remove(instanceDigest digest.Digest) error
SetURLs(instanceDigest digest.Digest, urls []string) error
URLs(instanceDigest digest.Digest) ([]string, error)
ClearAnnotations(instanceDigest *digest.Digest) error
SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error
Annotations(instanceDigest *digest.Digest) (map[string]string, error)
SetOS(instanceDigest digest.Digest, os string) error
@ -100,18 +101,21 @@ func (l *list) AddInstance(manifestDigest digest.Digest, manifestSize int64, man
Platform: schema2platform,
})
ociv1platform := v1.Platform{
ociv1platform := &v1.Platform{
Architecture: architecture,
OS: osName,
OSVersion: osVersion,
OSFeatures: osFeatures,
Variant: variant,
}
if ociv1platform.Architecture == "" && ociv1platform.OS == "" && ociv1platform.OSVersion == "" && ociv1platform.Variant == "" && len(ociv1platform.OSFeatures) == 0 {
ociv1platform = nil
}
l.oci.Manifests = append(l.oci.Manifests, v1.Descriptor{
MediaType: manifestType,
Size: manifestSize,
Digest: manifestDigest,
Platform: &ociv1platform,
Platform: ociv1platform,
})
return nil
@ -170,7 +174,13 @@ func (l *list) SetURLs(instanceDigest digest.Digest, urls []string) error {
return err
}
oci.URLs = append([]string{}, urls...)
if len(oci.URLs) == 0 {
oci.URLs = nil
}
docker.URLs = append([]string{}, urls...)
if len(docker.URLs) == 0 {
docker.URLs = nil
}
return nil
}
@ -183,7 +193,24 @@ func (l *list) URLs(instanceDigest digest.Digest) ([]string, error) {
return append([]string{}, oci.URLs...), nil
}
// SetAnnotations sets annotations on the image index, or on a specific manifest.
// ClearAnnotations removes all annotations from the image index, or from a
// specific manifest.
// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
func (l *list) ClearAnnotations(instanceDigest *digest.Digest) error {
a := &l.oci.Annotations
if instanceDigest != nil {
oci, err := l.findOCIv1(*instanceDigest)
if err != nil {
return err
}
a = &oci.Annotations
}
*a = nil
return nil
}
// SetAnnotations sets annotations on the image index, or on a specific
// manifest.
// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
func (l *list) SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error {
a := &l.oci.Annotations
@ -194,10 +221,15 @@ func (l *list) SetAnnotations(instanceDigest *digest.Digest, annotations map[str
}
a = &oci.Annotations
}
(*a) = make(map[string]string)
if *a == nil {
(*a) = make(map[string]string)
}
for k, v := range annotations {
(*a)[k] = v
}
if len(*a) == 0 {
*a = nil
}
return nil
}
@ -230,7 +262,13 @@ func (l *list) SetOS(instanceDigest digest.Digest, os string) error {
return err
}
docker.Platform.OS = os
if oci.Platform == nil {
oci.Platform = &v1.Platform{}
}
oci.Platform.OS = os
if oci.Platform.Architecture == "" && oci.Platform.OS == "" && oci.Platform.OSVersion == "" && oci.Platform.Variant == "" && len(oci.Platform.OSFeatures) == 0 {
oci.Platform = nil
}
return nil
}
@ -240,7 +278,11 @@ func (l *list) OS(instanceDigest digest.Digest) (string, error) {
if err != nil {
return "", err
}
return oci.Platform.OS, nil
platform := oci.Platform
if platform == nil {
platform = &v1.Platform{}
}
return platform.OS, nil
}
// SetArchitecture sets the Architecture field in the platform information associated with the instance with the specified digest.
@ -254,7 +296,13 @@ func (l *list) SetArchitecture(instanceDigest digest.Digest, arch string) error
return err
}
docker.Platform.Architecture = arch
if oci.Platform == nil {
oci.Platform = &v1.Platform{}
}
oci.Platform.Architecture = arch
if oci.Platform.Architecture == "" && oci.Platform.OS == "" && oci.Platform.OSVersion == "" && oci.Platform.Variant == "" && len(oci.Platform.OSFeatures) == 0 {
oci.Platform = nil
}
return nil
}
@ -264,7 +312,11 @@ func (l *list) Architecture(instanceDigest digest.Digest) (string, error) {
if err != nil {
return "", err
}
return oci.Platform.Architecture, nil
platform := oci.Platform
if platform == nil {
platform = &v1.Platform{}
}
return platform.Architecture, nil
}
// SetOSVersion sets the OSVersion field in the platform information associated with the instance with the specified digest.
@ -278,7 +330,13 @@ func (l *list) SetOSVersion(instanceDigest digest.Digest, osVersion string) erro
return err
}
docker.Platform.OSVersion = osVersion
if oci.Platform == nil {
oci.Platform = &v1.Platform{}
}
oci.Platform.OSVersion = osVersion
if oci.Platform.Architecture == "" && oci.Platform.OS == "" && oci.Platform.OSVersion == "" && oci.Platform.Variant == "" && len(oci.Platform.OSFeatures) == 0 {
oci.Platform = nil
}
return nil
}
@ -288,7 +346,11 @@ func (l *list) OSVersion(instanceDigest digest.Digest) (string, error) {
if err != nil {
return "", err
}
return oci.Platform.OSVersion, nil
platform := oci.Platform
if platform == nil {
platform = &v1.Platform{}
}
return platform.OSVersion, nil
}
// SetVariant sets the Variant field in the platform information associated with the instance with the specified digest.
@ -302,7 +364,13 @@ func (l *list) SetVariant(instanceDigest digest.Digest, variant string) error {
return err
}
docker.Platform.Variant = variant
if oci.Platform == nil {
oci.Platform = &v1.Platform{}
}
oci.Platform.Variant = variant
if oci.Platform.Architecture == "" && oci.Platform.OS == "" && oci.Platform.OSVersion == "" && oci.Platform.Variant == "" && len(oci.Platform.OSFeatures) == 0 {
oci.Platform = nil
}
return nil
}
@ -312,7 +380,11 @@ func (l *list) Variant(instanceDigest digest.Digest) (string, error) {
if err != nil {
return "", err
}
return oci.Platform.Variant, nil
platform := oci.Platform
if platform == nil {
platform = &v1.Platform{}
}
return platform.Variant, nil
}
// SetFeatures sets the features list in the platform information associated with the instance with the specified digest.
@ -323,6 +395,9 @@ func (l *list) SetFeatures(instanceDigest digest.Digest, features []string) erro
return err
}
docker.Platform.Features = append([]string{}, features...)
if len(docker.Platform.Features) == 0 {
docker.Platform.Features = nil
}
// no OCI equivalent
return nil
}
@ -348,7 +423,16 @@ func (l *list) SetOSFeatures(instanceDigest digest.Digest, osFeatures []string)
return err
}
docker.Platform.OSFeatures = append([]string{}, osFeatures...)
if oci.Platform == nil {
oci.Platform = &v1.Platform{}
}
oci.Platform.OSFeatures = append([]string{}, osFeatures...)
if len(oci.Platform.OSFeatures) == 0 {
oci.Platform.OSFeatures = nil
}
if oci.Platform.Architecture == "" && oci.Platform.OS == "" && oci.Platform.OSVersion == "" && oci.Platform.Variant == "" && len(oci.Platform.OSFeatures) == 0 {
oci.Platform = nil
}
return nil
}
@ -358,7 +442,11 @@ func (l *list) OSFeatures(instanceDigest digest.Digest) ([]string, error) {
if err != nil {
return nil, err
}
return append([]string{}, oci.Platform.OSFeatures...), nil
platform := oci.Platform
if platform == nil {
platform = &v1.Platform{}
}
return append([]string{}, platform.OSFeatures...), nil
}
// SetMediaType sets the MediaType field in the instance with the specified digest.

View File

@ -53,32 +53,32 @@ func golangConnectionCreate(options ConnectionCreateOptions) error {
dst.URI += uri.Path
}
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
if cfg.Engine.ServiceDestinations == nil {
cfg.Engine.ServiceDestinations = map[string]config.Destination{
options.Name: *dst,
}
cfg.Engine.ActiveService = options.Name
} else {
cfg.Engine.ServiceDestinations[options.Name] = *dst
}
// Create or update an existing farm with the connection being added
if options.Farm != "" {
if len(cfg.Farms.List) == 0 {
cfg.Farms.Default = options.Farm
}
if val, ok := cfg.Farms.List[options.Farm]; ok {
cfg.Farms.List[options.Farm] = append(val, options.Name)
// TODO this really should not live here, it must be in podman where we write the other connections as well.
// This duplicates the code for no reason and I have a really hard time to make any sense of why this code
// was added in the first place.
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
options.Name: *dst,
}
cfg.Connection.Default = options.Name
} else {
cfg.Farms.List[options.Farm] = []string{options.Name}
cfg.Connection.Connections[options.Name] = *dst
}
}
return cfg.Write()
// Create or update an existing farm with the connection being added
if options.Farm != "" {
if len(cfg.Farm.List) == 0 {
cfg.Farm.Default = options.Farm
}
if val, ok := cfg.Farm.List[options.Farm]; ok {
cfg.Farm.List[options.Farm] = append(val, options.Name)
} else {
cfg.Farm.List[options.Farm] = []string{options.Name}
}
}
return nil
})
}
func golangConnectionDial(options ConnectionDialOptions) (*ConnectionDialReport, error) {

View File

@ -72,24 +72,32 @@ func nativeConnectionCreate(options ConnectionCreateOptions) error {
return fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host)
}
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
if options.Default {
cfg.Engine.ActiveService = options.Name
}
if cfg.Engine.ServiceDestinations == nil {
cfg.Engine.ServiceDestinations = map[string]config.Destination{
options.Name: *dst,
// TODO this really should not live here, it must be in podman where we write the other connections as well.
// This duplicates the code for no reason and I have a really hard time to make any sense of why this code
// was added in the first place.
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
options.Name: *dst,
}
cfg.Connection.Default = options.Name
} else {
cfg.Connection.Connections[options.Name] = *dst
}
cfg.Engine.ActiveService = options.Name
} else {
cfg.Engine.ServiceDestinations[options.Name] = *dst
}
return cfg.Write()
// Create or update an existing farm with the connection being added
if options.Farm != "" {
if len(cfg.Farm.List) == 0 {
cfg.Farm.Default = options.Farm
}
if val, ok := cfg.Farm.List[options.Farm]; ok {
cfg.Farm.List[options.Farm] = append(val, options.Name)
} else {
cfg.Farm.List[options.Farm] = []string{options.Name}
}
}
return nil
})
}
func nativeConnectionExec(options ConnectionExecOptions) (*ConnectionExecReport, error) {

View File

@ -6,9 +6,9 @@ const (
// VersionMajor is for an API incompatible changes
VersionMajor = 5
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 29
VersionMinor = 30
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 2
VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = "-dev"

View File

@ -41,7 +41,7 @@ containers-storage: ## build using gc on the host
$(GO) build -compiler gc $(BUILDFLAGS) ./cmd/containers-storage
codespell:
codespell -S Makefile,build,buildah,buildah.spec,imgtype,copy,AUTHORS,bin,vendor,.git,go.sum,CHANGELOG.md,changelog.txt,seccomp.json,.cirrus.yml,"*.xz,*.gz,*.tar,*.tgz,*ico,*.png,*.1,*.5,*.orig,*.rej" -L worl,flate,uint,iff,od,ERRO -w
codespell -S Makefile,build,buildah,buildah.spec,imgtype,copy,AUTHORS,bin,vendor,.git,go.sum,CHANGELOG.md,changelog.txt,seccomp.json,.cirrus.yml,"*.xz,*.gz,*.tar,*.tgz,*ico,*.png,*.1,*.5,*.orig,*.rej" -L plack,worl,flate,uint,iff,od,ERRO -w
binary local-binary: containers-storage

View File

@ -73,11 +73,9 @@ type chunkedDiffer struct {
zstdReader *zstd.Decoder
rawReader io.Reader
// contentDigest is the digest of the uncompressed content
// (diffID) when the layer is fully retrieved. If the layer
// is not fully retrieved, instead of using the digest of the
// uncompressed content, it refers to the digest of the TOC.
contentDigest digest.Digest
// tocDigest is the digest of the TOC document when the layer
// is partially pulled.
tocDigest digest.Digest
// convertedToZstdChunked is set to true if the layer needs to
// be converted to the zstd:chunked format before it can be
@ -292,7 +290,7 @@ func makeZstdChunkedDiffer(ctx context.Context, store storage.Store, blobSize in
return nil, err
}
contentDigest, err := digest.Parse(annotations[internal.ManifestChecksumKey])
tocDigest, err := digest.Parse(annotations[internal.ManifestChecksumKey])
if err != nil {
return nil, fmt.Errorf("parse TOC digest %q: %w", annotations[internal.ManifestChecksumKey], err)
}
@ -300,7 +298,7 @@ func makeZstdChunkedDiffer(ctx context.Context, store storage.Store, blobSize in
return &chunkedDiffer{
fsVerityDigests: make(map[string]string),
blobSize: blobSize,
contentDigest: contentDigest,
tocDigest: tocDigest,
copyBuffer: makeCopyBuffer(),
fileType: fileTypeZstdChunked,
layersCache: layersCache,
@ -322,7 +320,7 @@ func makeEstargzChunkedDiffer(ctx context.Context, store storage.Store, blobSize
return nil, err
}
contentDigest, err := digest.Parse(annotations[estargz.TOCJSONDigestAnnotation])
tocDigest, err := digest.Parse(annotations[estargz.TOCJSONDigestAnnotation])
if err != nil {
return nil, fmt.Errorf("parse TOC digest %q: %w", annotations[estargz.TOCJSONDigestAnnotation], err)
}
@ -330,7 +328,7 @@ func makeEstargzChunkedDiffer(ctx context.Context, store storage.Store, blobSize
return &chunkedDiffer{
fsVerityDigests: make(map[string]string),
blobSize: blobSize,
contentDigest: contentDigest,
tocDigest: tocDigest,
copyBuffer: makeCopyBuffer(),
fileType: fileTypeEstargz,
layersCache: layersCache,
@ -1613,6 +1611,9 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
// stream to use for reading the zstd:chunked or Estargz file.
stream := c.stream
var uncompressedDigest digest.Digest
tocDigest := c.tocDigest
if c.convertToZstdChunked {
fd, err := unix.Open(dest, unix.O_TMPFILE|unix.O_RDWR|unix.O_CLOEXEC, 0o600)
if err != nil {
@ -1663,13 +1664,13 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
c.fileType = fileTypeZstdChunked
c.manifest = manifest
c.tarSplit = tarSplit
// since we retrieved the whole file and it was validated, use the diffID instead of the TOC digest.
c.contentDigest = diffID
c.tocOffset = tocOffset
// the file was generated by us and the digest for each file was already computed, no need to validate it again.
c.skipValidation = true
// since we retrieved the whole file and it was validated, do not use the TOC digest, but set the uncompressed digest.
tocDigest = ""
uncompressedDigest = diffID
}
lcd := chunkedLayerData{
@ -1698,7 +1699,8 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
Artifacts: map[string]interface{}{
tocKey: toc,
},
TOCDigest: c.contentDigest,
TOCDigest: tocDigest,
UncompressedDigest: uncompressedDigest,
}
if !parseBooleanPullOption(c.storeOpts, "enable_partial_images", false) {

6
vendor/modules.txt vendored
View File

@ -167,7 +167,7 @@ github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/pkg/volumes
github.com/containers/buildah/util
# github.com/containers/common v0.57.1-0.20240129201029-3310a75e3608
# github.com/containers/common v0.57.1-0.20240130143645-b26099256b92
## explicit; go 1.20
github.com/containers/common/internal/attributedstring
github.com/containers/common/libimage
@ -236,7 +236,7 @@ github.com/containers/conmon/runner/config
# github.com/containers/gvisor-tap-vsock v0.7.2
## explicit; go 1.20
github.com/containers/gvisor-tap-vsock/pkg/types
# github.com/containers/image/v5 v5.29.2-0.20240129204525-816800b5daf7
# github.com/containers/image/v5 v5.29.2-0.20240130233108-e66a1ade2efc
## explicit; go 1.19
github.com/containers/image/v5/copy
github.com/containers/image/v5/directory
@ -346,7 +346,7 @@ github.com/containers/psgo/internal/dev
github.com/containers/psgo/internal/host
github.com/containers/psgo/internal/proc
github.com/containers/psgo/internal/process
# github.com/containers/storage v1.52.1-0.20240129173630-7a525ce0e2bc
# github.com/containers/storage v1.52.1-0.20240130205044-62997abeaf2f
## explicit; go 1.20
github.com/containers/storage
github.com/containers/storage/drivers