config/file.go (133 lines of code) (raw):
// Copyright 2016 Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/GoogleCloudPlatform/docker-credential-gcr/v2/util"
)
const (
configFileEnvVariable = "DOCKER_CREDENTIAL_GCR_CONFIG"
configFileName = "docker_credential_gcr_config.json"
)
// DefaultTokenSources designates which default source(s) should be used to
// fetch a GCR access_token, and in which order.
var DefaultTokenSources = [...]string{"store", "env"}
// UserConfig describes
type UserConfig interface {
TokenSources() []string
SetTokenSources([]string) error
ResetAll() error
}
// configFile describes the structure of the persistent config store.
type configFile struct {
TokenSrcs []string `json:"TokenSources,omitempty"`
// package private helper, made a member variable and exposed for testing
persist func(*configFile) error
}
// LoadUserConfig returns the UserConfig which provides user-configurable
// application settings, or a new on if it doesn't exist.
func LoadUserConfig() (UserConfig, error) {
config, err := load()
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
config = &configFile{}
}
config.persist = persist
return config, nil
}
func load() (*configFile, error) {
path, err := configPath()
if err != nil {
return nil, err
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var config configFile
if err := json.NewDecoder(f).Decode(&config); err != nil {
return nil, fmt.Errorf("failed to load config from %s: %v", path, err)
}
return &config, nil
}
// TokenSources returns the configured token sources, or the DefaultTokenSources
// if none are set.
func (c *configFile) TokenSources() []string {
if len(c.TokenSrcs) == 0 { // if nil or empty
return DefaultTokenSources[:]
}
ret := make([]string, len(c.TokenSrcs))
copy(ret, c.TokenSrcs)
return ret
}
// SetTokenSources sets (and persists) the token sources. Valid token sources
// are defined by config.SupportedGCRTokenSources.
func (c *configFile) SetTokenSources(newSources []string) error {
if len(newSources) == 0 {
newSources = nil
}
// Don't touch the file unless we need to.
if equal(newSources, c.TokenSrcs) {
return nil
}
for _, source := range newSources {
if _, supported := SupportedGCRTokenSources[source]; !supported {
return fmt.Errorf("Unsupported token source: %s", source)
}
}
c.TokenSrcs = newSources
return c.persist(c)
}
func persist(c *configFile) error {
f, err := createConfigFile()
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(c)
}
func equal(a, b []string) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
// ResetAll clears all user configuration.
func (c *configFile) ResetAll() error {
err := deleteConfigFile()
if err != nil {
return err
}
c.TokenSrcs = nil
return nil
}
func deleteConfigFile() error {
path, err := configPath()
if err != nil && !os.IsNotExist(err) {
return err
}
return os.Remove(path)
}
// createConfigFile creates (or truncates) and returns an os.File for the
// user config.
func createConfigFile() (*os.File, error) {
path, err := configPath()
if err != nil {
return nil, err
}
// create the gcloud config dir, if it doesnt exist
if err = os.MkdirAll(filepath.Dir(path), 0777); err != nil {
return nil, err
}
// create or truncate the config file and return it
return os.Create(path)
}
// configPath returns the full path of our user config file.
func configPath() (string, error) {
if path := os.Getenv(configFileEnvVariable); strings.TrimSpace(path) != "" {
return path, nil
}
sdkConfigPath, err := util.SdkConfigPath()
if err != nil {
return "", fmt.Errorf("couldn't construct config path: %v", err)
}
return filepath.Join(sdkConfigPath, configFileName), nil
}