Merge pull request #21384 from Luap99/connections

rework system connection and farm storage
This commit is contained in:
openshift-merge-bot[bot] 2024-01-31 19:29:44 +00:00 committed by GitHub
commit 1a8cb15aa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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 {
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)
}
// Can create an empty farm without any connections
if len(connections) == 0 {
cfg.Farms.List[farmName] = []string{}
cfg.Farm.List[farmName] = []string{}
}
for _, c := range connections {
if _, ok := cfg.Engine.ServiceDestinations[c]; ok {
if slices.Contains(cfg.Farms.List[farmName], c) {
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.Farms.List[farmName] = append(cfg.Farms.List[farmName], c)
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.Farms.List) == 1 {
cfg.Farms.Default = farmName
if len(cfg.Farm.List) == 1 {
cfg.Farm.Default = farmName
}
err = cfg.Write()
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
farms, err := registry.PodmanConfig().ContainersConfDefaultsRO.GetAllFarms()
if err != nil {
return err
}
r := farmOut{
Name: k,
Connections: v,
Default: defaultFarm,
}
rows = append(rows, r)
}
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,18 +43,11 @@ func init() {
}
func rm(cmd *cobra.Command, args []string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
deletedFarms := []string{}
err := config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
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")
cfg.Farm.List = make(map[string][]string)
cfg.Farm.Default = ""
return nil
}
@ -63,20 +56,19 @@ func rm(cmd *cobra.Command, args []string) error {
return errors.New("requires at lease 1 arg(s), received 0")
}
if len(cfg.Farms.List) == 0 {
if len(cfg.Farm.List) == 0 {
return errors.New("no existing farms; nothing to remove")
}
deletedFarms := []string{}
for _, k := range args {
if _, ok := cfg.Farms.List[k]; !ok {
if _, ok := cfg.Farm.List[k]; !ok {
logrus.Warnf("farm %q doesn't exist; nothing to remove", k)
continue
}
delete(cfg.Farms.List, k)
delete(cfg.Farm.List, k)
deletedFarms = append(deletedFarms, k)
if k == cfg.Farms.Default {
cfg.Farms.Default = ""
if k == cfg.Farm.Default {
cfg.Farm.Default = ""
}
}
// Return error if none of the given farms were deleted
@ -85,15 +77,21 @@ func rm(cmd *cobra.Command, args []string) error {
}
// 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
if cfg.Farm.Default == "" && cfg.Farm.List != nil {
for k := range cfg.Farm.List {
cfg.Farm.Default = k
break
}
}
if err := cfg.Write(); err != nil {
return nil
})
if err != nil {
return err
}
if rmOpts.All {
fmt.Println("All farms have been deleted")
return nil
}
for _, k := range deletedFarms {
fmt.Printf("Farm %q deleted\n", k)

View File

@ -63,37 +63,33 @@ 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 {
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")
}
if _, ok := cfg.Farms.List[farmName]; !ok {
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.Farms.Default = farmName
cfg.Farm.Default = farmName
} else {
// if --default=false, user doesn't want any farms to be default so clear the DefaultFarm
cfg.Farms.Default = ""
cfg.Farm.Default = ""
}
}
if val, ok := cfg.Farms.List[farmName]; ok {
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.Farms.List[farmName]
connections := cfg.Farm.List[farmName]
if slices.Contains(connections, cRemove) {
delete(cMap, cRemove)
} else {
@ -102,7 +98,7 @@ func farmUpdate(cmd *cobra.Command, args []string) error {
}
for _, cAdd := range updateOpts.Add {
if _, ok := cfg.Engine.ServiceDestinations[cAdd]; ok {
if _, ok := cfg.Connection.Connections[cAdd]; ok {
if _, ok := cMap[cAdd]; !ok {
cMap[cAdd] = 0
}
@ -115,10 +111,11 @@ func farmUpdate(cmd *cobra.Command, args []string) error {
for k := range cMap {
updatedConnections = append(updatedConnections, k)
}
cfg.Farms.List[farmName] = updatedConnections
cfg.Farm.List[farmName] = updatedConnections
}
if err := cfg.Write(); err != nil {
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,17 +149,20 @@ 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 _, con := range cons {
if con.ReadWrite {
for _, connection := range []string{initOpts.Name, fmt.Sprintf("%s-root", initOpts.Name)} {
if _, valueFound := cfg.Engine.ServiceDestinations[connection]; valueFound {
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)
}
}
}
}
for idx, vol := range initOpts.Volumes {
initOpts.Volumes[idx] = os.ExpandEnv(vol)

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
}
func toHumanFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error) {
cfg, err := config.ReadCustomConfig()
if err != nil {
return nil, err
return machineResponses
}
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,29 +188,31 @@ 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:
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(),
Identity: cOpts.Identity,
}
if cmd.Flags().Changed("identity") {
dst.Identity = cOpts.Identity
connection := args[0]
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if cOpts.Default {
cfg.Connection.Default = connection
}
if cfg.Engine.ServiceDestinations == nil {
cfg.Engine.ServiceDestinations = map[string]config.Destination{
args[0]: dst,
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
connection: dst,
}
cfg.Engine.ActiveService = args[0]
cfg.Connection.Default = connection
} else {
cfg.Engine.ServiceDestinations[args[0]] = dst
cfg.Connection.Connections[connection] = dst
}
// Create or update an existing farm with the connection being added
if cOpts.Farm != "" {
if cfg.Farms.List == nil {
cfg.Farms.List = map[string][]string{
cOpts.Farm: {args[0]},
if len(cfg.Farm.List) == 0 {
cfg.Farm.Default = cOpts.Farm
}
cfg.Farms.Default = cOpts.Farm
if val, ok := cfg.Farm.List[cOpts.Farm]; ok {
cfg.Farm.List[cOpts.Farm] = append(val, connection)
} else {
if val, ok := cfg.Farms.List[cOpts.Farm]; ok {
cfg.Farms.List[cOpts.Farm] = append(val, args[0])
} 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{
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
args[0]: dst,
}
cfg.Engine.ActiveService = args[0]
cfg.Connection.Default = args[0]
} else {
cfg.Engine.ServiceDestinations[args[0]] = dst
cfg.Connection.Connections[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
@ -149,6 +130,7 @@ func inspect(cmd *cobra.Command, args []string) error {
"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 {
for k := range cfg.Engine.ServiceDestinations {
delete(cfg.Engine.ServiceDestinations, k)
}
cfg.Engine.ActiveService = ""
cfg.Connection.Connections = nil
cfg.Connection.Default = ""
// Clear all the connections in any existing farms
for k := range cfg.Farms.List {
cfg.Farms.List[k] = []string{}
for k := range cfg.Farm.List {
cfg.Farm.List[k] = []string{}
}
return cfg.Write()
return nil
}
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 = ""
delete(cfg.Connection.Connections, args[0])
if cfg.Connection.Default == args[0] {
cfg.Connection.Default = ""
}
// If there are existing farm, remove the deleted connection that might be part of a farm
for k, v := range cfg.Farms.List {
for k, v := range cfg.Farm.List {
index := slices.Index(v, args[0])
if index > -1 {
cfg.Farms.List[k] = append(v[:index], v[index+1:]...)
cfg.Farm.List[k] = append(v[:index], v[index+1:]...)
}
}
return cfg.Write()
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
}
if _, found := cfg.Engine.ServiceDestinations[args[0]]; !found {
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])
}
cfg.Engine.ServiceDestinations[args[1]] = cfg.Engine.ServiceDestinations[args[0]]
delete(cfg.Engine.ServiceDestinations, args[0])
cfg.Connection.Connections[args[1]] = cfg.Connection.Connections[args[0]]
delete(cfg.Connection.Connections, args[0])
if cfg.Engine.ActiveService == args[0] {
cfg.Engine.ActiveService = args[1]
if cfg.Connection.Default == args[0] {
cfg.Connection.Default = args[1]
}
return cfg.Write()
return nil
})
}

View File

@ -19,17 +19,18 @@ Change the default output format. This can be of a supported type like 'json' o
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 |
| .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
}
// Go through the connections in the farm and get their destination
for _, c := range cfg.Farms.List[name] {
dest[c] = cfg.Engine.ServiceDestinations[c]
// If no farm name is given, then grab all the service destinations available
cons, err := cfg.GetAllConnections()
return name, cons, err
}
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 config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
if _, ok := cfg.Connection.Connections[name]; ok {
return errors.New("cannot overwrite connection")
}
if isDefault {
cfg.Engine.ActiveService = name
}
dst := config.Destination{
URI: uri.String(),
IsMachine: true,
Identity: identity,
}
dst.Identity = identity
if cfg.Engine.ServiceDestinations == nil {
cfg.Engine.ServiceDestinations = map[string]config.Destination{
if isDefault {
cfg.Connection.Default = name
}
if cfg.Connection.Connections == nil {
cfg.Connection.Connections = map[string]config.Destination{
name: dst,
}
cfg.Engine.ActiveService = name
cfg.Connection.Default = name
} else {
cfg.Engine.ServiceDestinations[name] = dst
}
return cfg.Write()
cfg.Connection.Connections[name] = dst
}
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 false, nil
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]
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.Engine.ServiceDestinations[name] = dst
cfg.Connection.Connections[name] = dst
return cfg.Write()
return nil
})
}
func ChangeDefault(name string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
// 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
}
cfg.Engine.ActiveService = name
return cfg.Write()
return nil
})
}
func RemoveConnections(names ...string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
return err
}
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
for _, name := range names {
if _, ok := cfg.Engine.ServiceDestinations[name]; ok {
delete(cfg.Engine.ServiceDestinations, name)
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
if cfg.Connection.Default == name {
cfg.Connection.Default = ""
}
}
for service := range cfg.Connection.Connections {
cfg.Connection.Default = service
break
}
}
}
return cfg.Write()
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)
}
var err error
configs, err = addConfigs(OverrideContainersConfig+".d", configs)
@ -170,19 +184,15 @@ 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
}
}
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
}
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{
// 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.Engine.ActiveService = options.Name
cfg.Connection.Default = options.Name
} else {
cfg.Engine.ServiceDestinations[options.Name] = *dst
cfg.Connection.Connections[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 len(cfg.Farm.List) == 0 {
cfg.Farm.Default = options.Farm
}
if val, ok := cfg.Farms.List[options.Farm]; ok {
cfg.Farms.List[options.Farm] = append(val, options.Name)
if val, ok := cfg.Farm.List[options.Farm]; ok {
cfg.Farm.List[options.Farm] = append(val, options.Name)
} else {
cfg.Farms.List[options.Farm] = []string{options.Name}
cfg.Farm.List[options.Farm] = []string{options.Name}
}
}
return cfg.Write()
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{
// 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.Engine.ActiveService = options.Name
cfg.Connection.Default = options.Name
} else {
cfg.Engine.ServiceDestinations[options.Name] = *dst
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 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