1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
15
"github.com/siderolabs/crypto/x509"
19
// Config represents the client configuration file (talosconfig).
21
Context string `yaml:"context"`
22
Contexts map[string]*Context `yaml:"contexts"`
24
// path is the config Path config is read from.
28
// NewConfig returns the client configuration file with a single context.
29
func NewConfig(contextName string, endpoints []string, caCrt []byte, client *x509.PEMEncodedCertificateAndKey) *Config {
32
Contexts: map[string]*Context{
35
CA: base64.StdEncoding.EncodeToString(caCrt),
36
Crt: base64.StdEncoding.EncodeToString(client.Crt),
37
Key: base64.StdEncoding.EncodeToString(client.Key),
43
func (c *Config) upgrade() {
44
for _, ctx := range c.Contexts {
49
// Context represents the set of credentials required to talk to a target.
51
DeprecatedTarget string `yaml:"target,omitempty"` // Field deprecated in favor of Endpoints
52
Endpoints []string `yaml:"endpoints"`
53
Nodes []string `yaml:"nodes,omitempty"`
54
CA string `yaml:"ca,omitempty"`
55
Crt string `yaml:"crt,omitempty"`
56
Key string `yaml:"key,omitempty"`
57
Auth Auth `yaml:"auth,omitempty"`
58
Cluster string `yaml:"cluster,omitempty"`
61
// Auth may hold credentials for an authentication method such as Basic Auth.
63
Basic *Basic `yaml:"basic,omitempty"`
64
SideroV1 *SideroV1 `yaml:"siderov1,omitempty"`
67
// Basic holds Basic Auth credentials.
69
Username string `yaml:"username"`
70
Password string `yaml:"password"`
73
// SideroV1 holds information for SideroV1 API signature auth.
75
Identity string `yaml:"identity"`
78
func (c *Context) upgrade() {
79
if c.DeprecatedTarget != "" {
80
c.Endpoints = append(c.Endpoints, c.DeprecatedTarget)
81
c.DeprecatedTarget = ""
85
// Open reads the config and initializes a Config struct.
86
// If path is explicitly set, it will be used.
87
// If not, the default path rules will be used.
88
func Open(path string) (*Config, error) {
94
if path != "" { // path is explicitly specified, ensure that is created and use it
100
err = ensure(confPath.Path)
104
} else { // path is implicit, get the first already existing & readable path or ensure that it is created
105
confPath, err = firstValidPath()
111
config, err := fromFile(confPath.Path)
116
config.path = confPath
121
func fromFile(path string) (*Config, error) {
122
file, err := os.Open(path)
127
defer file.Close() //nolint:errcheck
129
return ReadFrom(file)
132
// FromString returns a config from a string.
133
func FromString(p string) (c *Config, err error) {
134
return ReadFrom(bytes.NewReader([]byte(p)))
137
// FromBytes returns a config from []byte.
138
func FromBytes(b []byte) (c *Config, err error) {
139
return ReadFrom(bytes.NewReader(b))
142
// ReadFrom reads a config from io.Reader.
143
func ReadFrom(r io.Reader) (c *Config, err error) {
146
if err = yaml.NewDecoder(r).Decode(c); err != nil {
155
// Save writes the config to disk.
156
// If the path is not explicitly set, the default path rules will be used.
157
func (c *Config) Save(path string) error {
160
if path != "" { // path is explicitly specified, use it
165
} else if c.path.Path == "" { // path is implicit and is not set on config, get the first already existing & writable path or create it
166
c.path, err = firstValidPath()
172
if !c.path.WriteAllowed {
173
return fmt.Errorf("not allowed to write to config: %s", c.path.Path)
176
configBytes, err := c.Bytes()
181
if err = os.MkdirAll(filepath.Dir(c.path.Path), 0o700); err != nil {
185
return os.WriteFile(c.path.Path, configBytes, 0o600)
188
// Bytes gets yaml encoded config data.
189
func (c *Config) Bytes() ([]byte, error) {
190
return yaml.Marshal(c)
193
// Path returns the filesystem path config was read from.
194
func (c *Config) Path() Path {
198
// Rename describes context rename during merge.
204
// String converts to "from" -> "to".
205
func (r *Rename) String() string {
206
return fmt.Sprintf("%q -> %q", r.From, r.To)
209
// Merge in additional contexts from another Config.
211
// Current context is overridden from passed in config.
212
func (c *Config) Merge(cfg *Config) []Rename {
213
if c.Contexts == nil {
214
c.Contexts = map[string]*Context{}
217
mappedContexts := map[string]string{}
218
renames := []Rename{}
220
for name, ctx := range cfg.Contexts {
223
if _, exists := c.Contexts[mergedName]; exists {
225
mergedName = fmt.Sprintf("%s-%d", name, i)
227
if _, exists := c.Contexts[mergedName]; !exists {
233
mappedContexts[name] = mergedName
235
if name != mergedName {
236
renames = append(renames, Rename{name, mergedName})
239
c.Contexts[mergedName] = ctx
242
if cfg.Context != "" {
243
c.Context = mappedContexts[cfg.Context]
249
func ensure(path string) error {
250
if _, err := os.Stat(path); os.IsNotExist(err) {
253
Contexts: map[string]*Context{},
256
return config.Save(path)