github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/docker_context.go (about)

     1  package client
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/opencontainers/go-digest"
    13  
    14  	"github.com/buildpacks/pack/pkg/logging"
    15  )
    16  
    17  const (
    18  	dockerHostEnvVar            = "DOCKER_HOST"
    19  	dockerConfigEnvVar          = "DOCKER_CONFIG"
    20  	defaultDockerRootConfigDir  = ".docker"
    21  	defaultDockerConfigFileName = "config.json"
    22  
    23  	dockerContextDirName      = "contexts"
    24  	dockerContextMetaDirName  = "meta"
    25  	dockerContextMetaFileName = "meta.json"
    26  	dockerContextEndpoint     = "docker"
    27  	defaultDockerContext      = "default"
    28  )
    29  
    30  type configFile struct {
    31  	CurrentContext string `json:"currentContext,omitempty"`
    32  }
    33  
    34  type endpoint struct {
    35  	Host string `json:",omitempty"`
    36  }
    37  
    38  /*
    39  	 Example Docker context file
    40  	 {
    41  	  "Name": "desktop-linux",
    42  	  "dockerConfigMetadata": {
    43  	    "Description": "Docker Desktop"
    44  	  },
    45  	  "Endpoints": {
    46  	    "docker": {
    47  	      "Host": "unix:///Users/jbustamante/.docker/run/docker.sock",
    48  	      "SkipTLSVerify": false
    49  	    }
    50  	  }
    51  	}
    52  */
    53  type dockerConfigMetadata struct {
    54  	Name      string              `json:",omitempty"`
    55  	Endpoints map[string]endpoint `json:"endpoints,omitempty"`
    56  }
    57  
    58  func ProcessDockerContext(logger logging.Logger) error {
    59  	dockerHost := os.Getenv(dockerHostEnvVar)
    60  	if dockerHost == "" {
    61  		dockerConfigDir, err := configDir()
    62  		if err != nil {
    63  			return err
    64  		}
    65  
    66  		logger.Debugf("looking for docker configuration file at: %s", dockerConfigDir)
    67  		configuration, err := readConfigFile(dockerConfigDir)
    68  		if err != nil {
    69  			return errors.Wrapf(err, "reading configuration file at '%s'", dockerConfigDir)
    70  		}
    71  
    72  		if skip(configuration) {
    73  			logger.Debug("docker context is default or empty, skipping it")
    74  			return nil
    75  		}
    76  
    77  		configMetaData, err := readConfigMetadata(dockerConfigDir, configuration.CurrentContext)
    78  		if err != nil {
    79  			return errors.Wrapf(err, "reading metadata for current context '%s' at '%s'", configuration.CurrentContext, dockerConfigDir)
    80  		}
    81  
    82  		if dockerEndpoint, ok := configMetaData.Endpoints[dockerContextEndpoint]; ok {
    83  			os.Setenv(dockerHostEnvVar, dockerEndpoint.Host)
    84  			logger.Debugf("using docker context '%s' with endpoint = '%s'", configuration.CurrentContext, dockerEndpoint.Host)
    85  		} else {
    86  			logger.Warnf("docker endpoint doesn't exist for context '%s'", configuration.CurrentContext)
    87  		}
    88  	} else {
    89  		logger.Debugf("'%s=%s' environment variable is being used", dockerHostEnvVar, dockerHost)
    90  	}
    91  	return nil
    92  }
    93  
    94  func configDir() (string, error) {
    95  	dir := os.Getenv(dockerConfigEnvVar)
    96  	if dir == "" {
    97  		home, err := os.UserHomeDir()
    98  		if err != nil {
    99  			return "", errors.Wrap(err, "determining user home directory")
   100  		}
   101  		dir = filepath.Join(home, defaultDockerRootConfigDir)
   102  	}
   103  	return dir, nil
   104  }
   105  
   106  func readConfigFile(configDir string) (*configFile, error) {
   107  	filename := filepath.Join(configDir, defaultDockerConfigFileName)
   108  	config := &configFile{}
   109  	file, err := os.Open(filename)
   110  	if err != nil {
   111  		if os.IsNotExist(err) {
   112  			return &configFile{}, nil
   113  		}
   114  		return &configFile{}, err
   115  	}
   116  	defer file.Close()
   117  	if err := json.NewDecoder(file).Decode(config); err != nil && !errors.Is(err, io.EOF) {
   118  		return &configFile{}, err
   119  	}
   120  	return config, nil
   121  }
   122  
   123  func readConfigMetadata(configDir string, context string) (dockerConfigMetadata, error) {
   124  	dockerContextDir := filepath.Join(configDir, dockerContextDirName)
   125  	metaFileName := filepath.Join(dockerContextDir, dockerContextMetaDirName, digest.FromString(context).Encoded(), dockerContextMetaFileName)
   126  	bytes, err := os.ReadFile(metaFileName)
   127  	if err != nil {
   128  		if errors.Is(err, os.ErrNotExist) {
   129  			return dockerConfigMetadata{}, fmt.Errorf("docker context '%s' not found", context)
   130  		}
   131  		return dockerConfigMetadata{}, err
   132  	}
   133  	var meta dockerConfigMetadata
   134  	if err := json.Unmarshal(bytes, &meta); err != nil {
   135  		return dockerConfigMetadata{}, fmt.Errorf("parsing %s: %v", metaFileName, err)
   136  	}
   137  	if meta.Name != context {
   138  		return dockerConfigMetadata{}, fmt.Errorf("context '%s' doesn't match metadata name '%s' at '%s'", context, meta.Name, metaFileName)
   139  	}
   140  
   141  	return meta, nil
   142  }
   143  
   144  func skip(configuration *configFile) bool {
   145  	return configuration == nil || configuration.CurrentContext == defaultDockerContext || configuration.CurrentContext == ""
   146  }