| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- package config
- import (
- "context"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "github.com/fatih/color"
- "github.com/sethvargo/go-envconfig"
- "gopkg.in/yaml.v2"
- )
- // ProfilesConfig is the top level config from the porter config file, containing all possible profiles.
- // This is only used when parsing and writing, and should now be passed around as config.
- // Instead, pass the specific profile to the relevant functions
- type ProfilesConfig struct {
- CurrentProfile string `yaml:"current_profile" env:"PORTER_PROFILE,default=default"`
- Profiles map[string]CLIConfig `yaml:"profiles"`
- }
- var defaultProfileName string = "default"
- // migrateExistingConfigYaml moves the existing config layout to the new config layout with profiles support.
- // This can be deprecated after 2025-01-01 as all tokens which were part of the old config, will have expired, meaning all users
- // will have had to log out
- func migrateExistingConfigYaml(configPath string) error {
- fi, err := os.ReadFile(filepath.Clean(configPath))
- if err != nil {
- return fmt.Errorf("error reading config file: %w", err)
- }
- // handle edge case where numbers are stored as strings for some values
- var existingConfig map[string]any
- err = yaml.Unmarshal(fi, &existingConfig)
- if err != nil {
- return fmt.Errorf("config file is invalid yaml: %w", err)
- }
- for k, v := range existingConfig {
- stringValue, ok := v.(string)
- if ok {
- valueAsInt, err := strconv.Atoi(stringValue)
- if err == nil {
- existingConfig[k] = valueAsInt
- }
- }
- }
- by, err := yaml.Marshal(existingConfig)
- if err != nil {
- return fmt.Errorf("unable to marshal existing config to bytes: %w", err)
- }
- var c CLIConfig
- err = yaml.Unmarshal(by, &c)
- if err != nil {
- return fmt.Errorf("config file is invalid yaml: %w", err)
- }
- err = updateValuesForSelectedProfile(defaultProfileName, configPath, withCLIConfig(c))
- if err != nil {
- return fmt.Errorf("unable to write migrated default config to file: %w", err)
- }
- err = updateCurrentProfileInFile(defaultProfileName, configPath)
- if err != nil {
- return fmt.Errorf("unable to update current_profile in config file: %w", err)
- }
- return nil
- }
- // writeProfileToProfilesConfigFile will write the given profile config to file, overwriting the entire existing file.
- // Ensure to call readProfilesConfigFromFile before running this function if you with to preserve current settings,
- // or call updateValuesForSelectedProfile to set specific values for a given profile
- func writeProfileToProfilesConfigFile(profilesConfig ProfilesConfig, configPath string) error {
- by, err := yaml.Marshal(profilesConfig)
- if err != nil {
- return fmt.Errorf("error marshalling profiles config: %w", err)
- }
- err = os.WriteFile(configPath, by, os.ModePerm)
- if err != nil {
- return fmt.Errorf("error writing profiles config to file")
- }
- return nil
- }
- // updateCurrentProfileInFile changes the current_profile that is selected in the file for the next run.
- func updateCurrentProfileInFile(newProfile string, configPath string) error {
- if newProfile == "" {
- return errors.New("cannot update profile to an empty profile")
- }
- profilesConfig, err := readProfilesConfigFromFile(configPath)
- if err != nil {
- return fmt.Errorf("error reading profiles config file for updating current profile: %w", err)
- }
- profilesConfig.CurrentProfile = newProfile
- err = writeProfileToProfilesConfigFile(profilesConfig, configPath)
- if err != nil {
- return fmt.Errorf("unable to update current profile in config file: %w", err)
- }
- return nil
- }
- // updateValuesForSelectedProfile updates config for a given profile. This can be used with the --profile flag or the PORTER_PROFILE env var,
- // and will not necessarily update the current_profile in the config file.
- func updateValuesForSelectedProfile(selectedProfile string, configPath string, updatedCLIValues ...cliConfigValue) error {
- if selectedProfile == "" {
- return errors.New("must specify a profile to update")
- }
- profilesConfig, err := readProfilesConfigFromFile(configPath)
- if err != nil {
- return fmt.Errorf("error reading profiles config file for updating selected profile: %w", err)
- }
- if profilesConfig.Profiles == nil {
- profilesConfig.Profiles = map[string]CLIConfig{
- selectedProfile: defaultCLIConfig(),
- }
- }
- if _, ok := profilesConfig.Profiles[selectedProfile]; !ok {
- profilesConfig.Profiles[selectedProfile] = defaultCLIConfig()
- }
- baseProfile := profilesConfig.Profiles[selectedProfile]
- for _, v := range updatedCLIValues {
- v(&baseProfile)
- }
- profilesConfig.Profiles[selectedProfile] = baseProfile
- err = writeProfileToProfilesConfigFile(profilesConfig, configPath)
- if err != nil {
- return fmt.Errorf("unable to update current profile in config file: %w", err)
- }
- return nil
- }
- // readProfilesConfigFromFile reads the config file which supports profiles
- func readProfilesConfigFromFile(configPath string) (ProfilesConfig, error) {
- var profiles ProfilesConfig
- fi, err := os.ReadFile(filepath.Clean(configPath))
- if err != nil {
- return profiles, fmt.Errorf("error reading config file: %w", err)
- }
- err = yaml.Unmarshal(fi, &profiles)
- if err != nil {
- return profiles, fmt.Errorf("config file is invalid yaml: %w", err)
- }
- return profiles, nil
- }
- // defaultCLIConfig sets the default values for give profile
- func defaultCLIConfig() CLIConfig {
- return CLIConfig{
- Driver: "local",
- Host: "https://dashboard.getporter.dev",
- Project: 0,
- Cluster: 0,
- Token: "",
- Registry: 0,
- HelmRepo: 0,
- }
- }
- // ensurePorterConfigDirectoryExists checks that the .porter folder exists, and creates it if it doesn't exist
- func ensurePorterConfigDirectoryExists() error {
- _, err := os.Stat(defaultPorterConfigDir)
- if err != nil {
- if !os.IsNotExist(err) {
- return fmt.Errorf("error reading porter directory: %w", err)
- }
- err = os.Mkdir(defaultPorterConfigDir, 0o700)
- if err != nil {
- return fmt.Errorf("error creating porter directory: %w", err)
- }
- }
- return nil
- }
- // ensurePorterConfigFileExists checks that the porter.yaml config file exists, and creates it if it doesn't exist
- func ensurePorterConfigFileExists() error {
- _, err := os.Stat(porterConfigFilePath)
- if err != nil {
- if !os.IsNotExist(err) {
- return fmt.Errorf("error reading porter config file: %w", err)
- }
- by, _ := yaml.Marshal(defaultCLIConfig())
- err = os.WriteFile(porterConfigFilePath, by, 0o664)
- if err != nil {
- if !errors.Is(err, os.ErrExist) {
- return fmt.Errorf("error creating porter config file: %w", err)
- }
- }
- }
- return nil
- }
- // overlayProfiles will add all values from the profileToOverlay to the baseProfile,
- // returning the new profile with both values
- //
- //nolint:unparam
- func overlayProfiles(baseProfile CLIConfig, profileToOverlay CLIConfig) (CLIConfig, error) {
- if profileToOverlay.Cluster != 0 {
- baseProfile.Cluster = profileToOverlay.Cluster
- }
- if profileToOverlay.Driver != "" {
- baseProfile.Driver = profileToOverlay.Driver
- }
- if profileToOverlay.HelmRepo != 0 {
- baseProfile.HelmRepo = profileToOverlay.HelmRepo
- }
- if profileToOverlay.Host != "" {
- baseProfile.Host = profileToOverlay.Host
- }
- if profileToOverlay.Kubeconfig != "" {
- baseProfile.Kubeconfig = profileToOverlay.Kubeconfig
- }
- if profileToOverlay.Project != 0 {
- baseProfile.Project = profileToOverlay.Project
- }
- if profileToOverlay.Registry != 0 {
- baseProfile.Registry = profileToOverlay.Registry
- }
- if profileToOverlay.Token != "" {
- baseProfile.Token = profileToOverlay.Token
- }
- return baseProfile, nil
- }
- // configForProfileFromConfigFile gets the profile for the current_profile specified in the porter config file
- func configForProfileFromConfigFile(selectedProfile string, configPath string) (CLIConfig, string, error) {
- if selectedProfile == "" {
- selectedProfile = defaultProfileName
- }
- profilesConfig, err := readProfilesConfigFromFile(configPath)
- if err != nil {
- return CLIConfig{}, selectedProfile, fmt.Errorf("error reading profiles config file: %w", err)
- }
- if selectedProfile == defaultProfileName {
- if profilesConfig.CurrentProfile != "" {
- selectedProfile = profilesConfig.CurrentProfile
- }
- }
- if profilesConfig.CurrentProfile == "" && len(profilesConfig.Profiles) == 0 {
- err := migrateExistingConfigYaml(configPath)
- if err != nil {
- return CLIConfig{}, selectedProfile, fmt.Errorf("error migrating porter.yaml config file. Please delete file at %s. %w", configPath, err)
- }
- migrated, selectedProfile, err := configForProfileFromConfigFile(selectedProfile, configPath)
- if err != nil {
- return CLIConfig{}, selectedProfile, fmt.Errorf("error migrating existing porter.yaml to support profiles: %w", err)
- }
- return migrated, selectedProfile, nil
- }
- configFile, ok := profilesConfig.Profiles[selectedProfile]
- if !ok {
- _, _ = color.New(color.FgGreen).Printf("Porter profile '%s' does not exist. Creating one now...\n", currentProfile)
- err = updateValuesForSelectedProfile(selectedProfile, configPath, withCLIConfig(defaultCLIConfig()))
- if err != nil {
- return CLIConfig{}, selectedProfile, fmt.Errorf("error creating new profile: %w", err)
- }
- }
- return configFile, selectedProfile, nil
- }
- // profileConfigFromEnvVars parses any environment variables that may be setting
- // config values, such as PORTER_HOST and PORTER_PROJECT
- func profileConfigFromEnvVars(ctx context.Context) (CLIConfig, error) {
- var c CLIConfig
- if err := envconfig.Process(ctx, &c); err != nil {
- return c, fmt.Errorf("error processing porter env vars: %w", err)
- }
- return c, nil
- }
|