github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/jujuclient/accounts.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package jujuclient
     5  
     6  import (
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/utils/v3"
    13  	"gopkg.in/yaml.v2"
    14  
    15  	"github.com/juju/juju/juju/osenv"
    16  )
    17  
    18  // JujuAccountsPath is the location where accounts information is
    19  // expected to be found.
    20  func JujuAccountsPath() string {
    21  	return osenv.JujuXDGDataHomePath("accounts.yaml")
    22  }
    23  
    24  // ReadAccountsFile loads all accounts defined in a given file.
    25  // If the file is not found, it is not an error.
    26  func ReadAccountsFile(file string) (map[string]AccountDetails, error) {
    27  	data, err := os.ReadFile(file)
    28  	if err != nil {
    29  		if os.IsNotExist(err) {
    30  			return nil, nil
    31  		}
    32  		return nil, err
    33  	}
    34  	accounts, err := ParseAccounts(data)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	if err := migrateLocalAccountUsers(accounts); err != nil {
    39  		return nil, err
    40  	}
    41  	return accounts, nil
    42  }
    43  
    44  func migrateLocalAccountUsers(accounts map[string]AccountDetails) error {
    45  	changes := false
    46  	for user, account := range accounts {
    47  		if !strings.HasSuffix(account.User, "@local") {
    48  			continue
    49  		}
    50  		tag := names.NewUserTag(account.User)
    51  		updated := account
    52  		updated.User = tag.Id()
    53  		accounts[user] = updated
    54  		changes = true
    55  	}
    56  	if changes {
    57  		return WriteAccountsFile(accounts)
    58  	}
    59  	return nil
    60  }
    61  
    62  // WriteAccountsFile marshals to YAML details of the given accounts
    63  // and writes it to the accounts file.
    64  func WriteAccountsFile(controllerAccounts map[string]AccountDetails) error {
    65  	data, err := yaml.Marshal(accountsCollection{controllerAccounts})
    66  	if err != nil {
    67  		return errors.Annotate(err, "cannot marshal accounts")
    68  	}
    69  	return utils.AtomicWriteFile(JujuAccountsPath(), data, os.FileMode(0600))
    70  }
    71  
    72  // ParseAccounts parses the given YAML bytes into accounts metadata.
    73  func ParseAccounts(data []byte) (map[string]AccountDetails, error) {
    74  	var result accountsCollection
    75  	if err := yaml.Unmarshal(data, &result); err != nil {
    76  		return nil, errors.Annotate(err, "cannot unmarshal accounts")
    77  	}
    78  	return result.ControllerAccounts, nil
    79  }
    80  
    81  type accountsCollection struct {
    82  	ControllerAccounts map[string]AccountDetails `yaml:"controllers"`
    83  }