iclient/config.go
Rama develop · réplica de lectura del repositorio original.
package iclient
import (
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"github.com/lxc/incus/v7/shared/util"
)
// ConfigRemoteTLS holds inline TLS material for a remote.
type ConfigRemoteTLS struct {
Certificate string `json:"certificate"`
Key string `json:"key"`
CA string `json:"ca"`
}
// ConfigRemote holds the details for communicating with one Incus daemon.
type ConfigRemote struct {
Addrs []string `yaml:"-"`
LastWorkingAddr string `yaml:"last_working_address,omitempty"`
AuthType string `yaml:"auth_type,omitempty"`
KeepAlive int `yaml:"keepalive,omitempty"`
Protocol string `yaml:"protocol,omitempty"`
CredHelper string `yaml:"credentials_helper,omitempty"`
Public bool `yaml:"public"`
Global bool `yaml:"-"`
Static bool `yaml:"-"`
TLS *ConfigRemoteTLS `yaml:"-"`
}
// UnmarshalYAML reads `addr` as a comma-separated list into Addrs.
func (r *ConfigRemote) UnmarshalYAML(unmarshal func(any) error) error {
type R ConfigRemote
tmp := struct {
*R `yaml:",inline"`
Addrs string `yaml:"addr"`
}{
R: (*R)(r),
}
err := unmarshal(&tmp)
if err != nil {
return err
}
r.Addrs = util.SplitNTrimSpace(tmp.Addrs, ",", -1, true)
if r.Addrs == nil {
return errors.New("remotes must have at least one address")
}
return nil
}
// rollingAddrs returns the addresses starting at the last known working one.
func (r ConfigRemote) rollingAddrs() []string {
if len(r.Addrs) == 0 {
return nil
}
start := 0
if r.LastWorkingAddr != "" {
for i, addr := range r.Addrs {
if addr == r.LastWorkingAddr {
start = i
break
}
}
}
out := make([]string, 0, len(r.Addrs))
for i := range r.Addrs {
out = append(out, r.Addrs[(start+i)%len(r.Addrs)])
}
return out
}
// Config is the parsed Incus CLI configuration.
type Config struct {
DefaultRemote string `yaml:"default-remote"`
Remotes map[string]ConfigRemote `yaml:"remotes"`
// ConfigDir is where the configuration was read from; the certificate
// paths hang off it.
ConfigDir string `yaml:"-"`
// UserAgent is sent with every request.
UserAgent string `yaml:"-"`
// creds memoises what each remote's credentials helper answered, so a run
// resolving one registry for a dozen images spawns the helper once. Every
// remote gets an entry up front, so this map is only ever read.
creds map[string]*credEntry
}
// ConfigLocalRemote is the unix socket remote, which cannot be removed.
var ConfigLocalRemote = ConfigRemote{
Addrs: []string{"unix://"},
Static: true,
Public: false,
Protocol: "incus",
}
// ConfigImagesRemote is the community image server.
var ConfigImagesRemote = ConfigRemote{
Addrs: []string{"https://images.linuxcontainers.org"},
Public: true,
Protocol: "simplestreams",
}
// ConfigDefaultRemotes is the set used when there is no configuration file.
var ConfigDefaultRemotes = map[string]ConfigRemote{
"images": ConfigImagesRemote,
"local": ConfigLocalRemote,
}
// ConfigDefaultConfig returns the configuration used when no file exists.
func ConfigDefaultConfig() *Config {
c := &Config{
Remotes: maps.Clone(ConfigDefaultRemotes),
DefaultRemote: "local",
}
c.prefillCreds()
return c
}
// prefillCreds gives every remote its credential entry, so resolving one later
// only reads the map and needs no lock of its own.
func (c *Config) prefillCreds() {
c.creds = make(map[string]*credEntry, len(c.Remotes))
for name := range c.Remotes {
c.creds[name] = &credEntry{}
}
}
// configGlobalPath joins the system-wide configuration directory.
func configGlobalPath(paths ...string) string {
dir := "/etc/incus"
v, ok := os.LookupEnv("INCUS_GLOBAL_CONF")
if ok {
dir = v
}
return filepath.Join(append([]string{dir}, paths...)...)
}
// Path joins the configuration directory with the given elements.
func (c *Config) Path(paths ...string) string {
return filepath.Join(append([]string{c.ConfigDir}, paths...)...)
}
// ServerCertPath returns where the remote's pinned server certificate lives.
func (c *Config) ServerCertPath(remote string) string {
if c.Remotes[remote].Global {
return configGlobalPath("servercerts", fmt.Sprintf("%s.crt", remote))
}
return c.Path("servercerts", fmt.Sprintf("%s.crt", remote))
}
// clientCertPaths returns the certificate, key and CA paths for a remote,
// preferring a per-remote set over the shared one.
func (c *Config) clientCertPaths(remote string) (cert, key, ca string) {
perRemote := c.Path("clientcerts", fmt.Sprintf("%s.crt", remote))
if util.PathExists(perRemote) {
return perRemote,
c.Path("clientcerts", fmt.Sprintf("%s.key", remote)),
c.Path("clientcerts", fmt.Sprintf("%s.ca", remote))
}
return c.Path("client.crt"), c.Path("client.key"), c.Path("client.ca")
}
// readIfPresent returns the file's contents, or "" when it does not exist.
func readIfPresent(path string) (string, error) {
if !util.PathExists(path) {
return "", nil
}
content, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("reading %s: %w", path, err)
}
return string(content), nil
}