191 lines
4.4 KiB
Go
191 lines
4.4 KiB
Go
// 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/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
|
|
}
|