github.com/pachyderm/pachyderm@v1.13.4/src/client/pkg/config/config.go (about) 1 package config 2 3 import ( 4 "encoding/json" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "sync" 9 10 "github.com/gogo/protobuf/proto" 11 "github.com/pachyderm/pachyderm/src/client/pkg/errors" 12 "github.com/pachyderm/pachyderm/src/client/pkg/grpcutil" 13 uuid "github.com/satori/go.uuid" 14 log "github.com/sirupsen/logrus" 15 ) 16 17 const ( 18 configEnvVar = "PACH_CONFIG" 19 contextEnvVar = "PACH_CONTEXT" 20 ) 21 22 var defaultConfigDir = filepath.Join(os.Getenv("HOME"), ".pachyderm") 23 var defaultConfigPath = filepath.Join(defaultConfigDir, "config.json") 24 var pachctlConfigPath = filepath.Join("/pachctl", "config.json") 25 26 var configMu sync.Mutex 27 var value *Config 28 29 func configPath() string { 30 if env, ok := os.LookupEnv(configEnvVar); ok { 31 return env 32 } 33 if _, err := os.Stat(pachctlConfigPath); err == nil { 34 return pachctlConfigPath 35 } 36 return defaultConfigPath 37 } 38 39 // ActiveContext gets the active context in the config 40 func (c *Config) ActiveContext(errorOnNoActive bool) (string, *Context, error) { 41 if c.V2 == nil { 42 return "", nil, errors.Errorf("cannot get active context from non-v2 config") 43 } 44 if envContext, ok := os.LookupEnv(contextEnvVar); ok { 45 context := c.V2.Contexts[envContext] 46 if context == nil { 47 return "", nil, errors.Errorf("pachctl config error: `%s` refers to a context (%q) that does not exist", contextEnvVar, envContext) 48 } 49 return envContext, context, nil 50 } 51 context := c.V2.Contexts[c.V2.ActiveContext] 52 if context == nil { 53 if c.V2.ActiveContext == "" { 54 if errorOnNoActive { 55 return "", nil, errors.Errorf("pachctl config error: no active " + 56 "context configured.\n\nYou can fix your config by setting " + 57 "the active context like so: pachctl config set " + 58 "active-context <context>") 59 } 60 } else { 61 return "", nil, errors.Errorf("pachctl config error: pachctl's active "+ 62 "context is %q, but no context named %q has been configured.\n\nYou can fix "+ 63 "your config by setting the active context like so: pachctl config set "+ 64 "active-context <context>", 65 c.V2.ActiveContext, c.V2.ActiveContext) 66 } 67 68 } 69 return c.V2.ActiveContext, context, nil 70 } 71 72 // Read loads the Pachyderm config on this machine. 73 // If an existing configuration cannot be found, it sets up the defaults. Read 74 // returns a nil Config if and only if it returns a non-nil error. 75 func Read(ignoreCache, readOnly bool) (*Config, error) { 76 configMu.Lock() 77 defer configMu.Unlock() 78 79 if value == nil || ignoreCache { 80 // Read json file 81 p := configPath() 82 if raw, err := ioutil.ReadFile(p); err == nil { 83 err = json.Unmarshal(raw, &value) 84 if err != nil { 85 return nil, errors.Wrapf(err, "could not parse config json at %q", p) 86 } 87 } else if errors.Is(err, os.ErrNotExist) { 88 // File doesn't exist, so create a new config 89 log.Debugf("No config detected at %q. Generating new config...", p) 90 value = &Config{} 91 } else { 92 return nil, errors.Wrapf(err, "could not read config at %q", p) 93 } 94 95 updated := false 96 97 if value.UserID == "" { 98 updated = true 99 log.Debugln("No UserID present in config - generating new one.") 100 uuid := uuid.NewV4() 101 value.UserID = uuid.String() 102 } 103 104 if value.V2 == nil { 105 updated = true 106 log.Debugln("No config V2 present in config - generating a new one.") 107 if err := value.InitV2(); err != nil { 108 return nil, err 109 } 110 } 111 112 for contextName, context := range value.V2.Contexts { 113 pachdAddress, err := grpcutil.ParsePachdAddress(context.PachdAddress) 114 if err != nil { 115 if !errors.Is(err, grpcutil.ErrNoPachdAddress) { 116 return nil, errors.Wrapf(err, "could not parse pachd address for context '%s'", contextName) 117 } 118 } else { 119 if qualifiedPachdAddress := pachdAddress.Qualified(); qualifiedPachdAddress != context.PachdAddress { 120 log.Debugf("Non-qualified pachd address set for context '%s' - fixing", contextName) 121 context.PachdAddress = qualifiedPachdAddress 122 updated = true 123 } 124 } 125 } 126 127 if updated && !readOnly { 128 log.Debugf("Rewriting config at %q.", p) 129 130 if err := value.Write(); err != nil { 131 return nil, errors.Wrapf(err, "could not rewrite config at %q", p) 132 } 133 } 134 } 135 136 cloned := proto.Clone(value).(*Config) 137 // in the case of an empty map, `proto.Clone` incorrectly clones 138 // `Contexts` as nil. This fixes the issue. 139 if cloned.V2.Contexts == nil { 140 cloned.V2.Contexts = map[string]*Context{} 141 } 142 143 return cloned, nil 144 } 145 146 // InitV2 initializes the V2 object of the config 147 func (c *Config) InitV2() error { 148 c.V2 = &ConfigV2{ 149 ActiveContext: "default", 150 Contexts: map[string]*Context{}, 151 Metrics: true, 152 } 153 154 if c.V1 != nil { 155 c.V2.Contexts["default"] = &Context{ 156 Source: ContextSource_CONFIG_V1, 157 PachdAddress: c.V1.PachdAddress, 158 ServerCAs: c.V1.ServerCAs, 159 SessionToken: c.V1.SessionToken, 160 ActiveTransaction: c.V1.ActiveTransaction, 161 } 162 163 c.V1 = nil 164 } else { 165 c.V2.Contexts["default"] = &Context{ 166 Source: ContextSource_NONE, 167 } 168 } 169 return nil 170 } 171 172 // Write writes the configuration in 'c' to this machine's Pachyderm config 173 // file. 174 func (c *Config) Write() error { 175 if c.V1 != nil { 176 panic("config V1 included (this is a bug)") 177 } 178 179 rawConfig, err := json.MarshalIndent(c, "", " ") 180 if err != nil { 181 return err 182 } 183 184 p := configPath() 185 186 // Because we're writing the config back to disk, we'll also need to make sure 187 // that the directory we're writing the config into exists. The approach we 188 // use for doing this depends on whether PACH_CONFIG is set. 189 if _, ok := os.LookupEnv(configEnvVar); ok { 190 // using overridden config path: check that the parent dir exists, but don't 191 // create any new directories 192 d := filepath.Dir(p) 193 if _, err := os.Stat(d); err != nil { 194 return errors.Wrapf(err, "cannot use config at %s: could not stat parent directory", p) 195 } 196 } else { 197 // using the default config path, create the config directory 198 err = os.MkdirAll(defaultConfigDir, 0755) 199 if err != nil { 200 return err 201 } 202 } 203 204 // Write to a temporary file first, then rename the temporary file to `p`. 205 // This ensures the write is atomic on POSIX. 206 tmpfile, err := ioutil.TempFile("", "pachyderm-config-*.json") 207 if err != nil { 208 return err 209 } 210 defer os.Remove(tmpfile.Name()) 211 212 if _, err = tmpfile.Write(rawConfig); err != nil { 213 return err 214 } 215 if err = tmpfile.Close(); err != nil { 216 return err 217 } 218 if err = os.Rename(tmpfile.Name(), p); err != nil { 219 // A rename could fail if the temporary directory is mounted on a 220 // different device than the config path. If the rename failed, try to 221 // just copy the bytes instead. 222 if err = ioutil.WriteFile(p, rawConfig, 0644); err != nil { 223 return errors.Wrapf(err, "failed to write config file") 224 } 225 } 226 227 // essentially short-cuts reading the new config back from disk 228 value = proto.Clone(c).(*Config) 229 return nil 230 } 231 232 // WritePachTokenToConfig sets the auth token for the current pachctl config. 233 // Used during tests to ensure we don't lose access to a cluster if a test fails. 234 func WritePachTokenToConfig(token string) error { 235 cfg, err := Read(false, false) 236 if err != nil { 237 return errors.Wrapf(err, "error reading Pachyderm config (for cluster address)") 238 } 239 _, context, err := cfg.ActiveContext(true) 240 if err != nil { 241 return errors.Wrapf(err, "error getting the active context") 242 } 243 context.SessionToken = token 244 if err := cfg.Write(); err != nil { 245 return errors.Wrapf(err, "error writing pachyderm config") 246 } 247 return nil 248 }