|  | @@ -104,35 +104,31 @@ func New(opts ...Opt) (Store, error) {
 | 
	
		
			
				|  |  |  	if err != nil {
 | 
	
		
			
				|  |  |  		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	s := &store{
 | 
	
		
			
				|  |  | -		root: filepath.Join(home, configDir),
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	root := filepath.Join(home, configDir)
 | 
	
		
			
				|  |  | +	if err := createDirIfNotExist(root); err != nil {
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	if _, err := os.Stat(s.root); os.IsNotExist(err) {
 | 
	
		
			
				|  |  | -		if err = os.Mkdir(s.root, 0755); err != nil {
 | 
	
		
			
				|  |  | -			return nil, err
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	s := &store{
 | 
	
		
			
				|  |  | +		root: root,
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	for _, opt := range opts {
 | 
	
		
			
				|  |  |  		opt(s)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	cd := filepath.Join(s.root, contextsDir)
 | 
	
		
			
				|  |  | -	if _, err := os.Stat(cd); os.IsNotExist(err) {
 | 
	
		
			
				|  |  | -		if err = os.Mkdir(cd, 0755); err != nil {
 | 
	
		
			
				|  |  | -			return nil, err
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	m := filepath.Join(cd, metadataDir)
 | 
	
		
			
				|  |  | -	if _, err := os.Stat(m); os.IsNotExist(err) {
 | 
	
		
			
				|  |  | -		if err = os.Mkdir(m, 0755); err != nil {
 | 
	
		
			
				|  |  | -			return nil, err
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	m := filepath.Join(s.root, contextsDir, metadataDir)
 | 
	
		
			
				|  |  | +	if err := createDirIfNotExist(m); err != nil {
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	return s, nil
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // Get returns the context with the given name
 | 
	
		
			
				|  |  |  func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
 | 
	
		
			
				|  |  | -	meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile)
 | 
	
		
			
				|  |  | +	meta := filepath.Join(s.root, contextsDir, metadataDir, contextDirOf(name), metaFile)
 | 
	
		
			
				|  |  |  	m, err := read(meta, getter)
 | 
	
		
			
				|  |  |  	if os.IsNotExist(err) {
 | 
	
		
			
				|  |  |  		return nil, errors.Wrap(errdefs.ErrNotFound, objectName(name))
 | 
	
	
		
			
				|  | @@ -150,31 +146,42 @@ func read(meta string, getter func() interface{}) (*Metadata, error) {
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	var um untypedMetadata
 | 
	
		
			
				|  |  | -	if err := json.Unmarshal(bytes, &um); err != nil {
 | 
	
		
			
				|  |  | +	if err := marshalTyped(bytes, &um); err != nil {
 | 
	
		
			
				|  |  |  		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	var uc untypedContext
 | 
	
		
			
				|  |  | -	if err := json.Unmarshal(um.Metadata, &uc); err != nil {
 | 
	
		
			
				|  |  | +	if err := marshalTyped(um.Metadata, &uc); err != nil {
 | 
	
		
			
				|  |  |  		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +	if uc.Type == "" {
 | 
	
		
			
				|  |  | +		uc.Type = "docker"
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	data, err := parse(uc.Data, getter)
 | 
	
		
			
				|  |  | -	if err != nil {
 | 
	
		
			
				|  |  | -		return nil, err
 | 
	
		
			
				|  |  | +	var data interface{}
 | 
	
		
			
				|  |  | +	if uc.Data != nil {
 | 
	
		
			
				|  |  | +		data, err = parse(uc.Data, getter)
 | 
	
		
			
				|  |  | +		if err != nil {
 | 
	
		
			
				|  |  | +			return nil, err
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	return &Metadata{
 | 
	
		
			
				|  |  |  		Name:      um.Name,
 | 
	
		
			
				|  |  |  		Endpoints: um.Endpoints,
 | 
	
		
			
				|  |  |  		Metadata: TypedContext{
 | 
	
		
			
				|  |  | -			Description: uc.Description,
 | 
	
		
			
				|  |  | -			Type:        uc.Type,
 | 
	
		
			
				|  |  | -			Data:        data,
 | 
	
		
			
				|  |  | +			StackOrchestrator: uc.StackOrchestrator,
 | 
	
		
			
				|  |  | +			Description:       uc.Description,
 | 
	
		
			
				|  |  | +			Type:              uc.Type,
 | 
	
		
			
				|  |  | +			Data:              data,
 | 
	
		
			
				|  |  |  		},
 | 
	
		
			
				|  |  |  	}, nil
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +func marshalTyped(in []byte, val interface{}) error {
 | 
	
		
			
				|  |  | +	return json.Unmarshal(in, val)
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  func parse(payload []byte, getter func() interface{}) (interface{}, error) {
 | 
	
		
			
				|  |  |  	if getter == nil {
 | 
	
		
			
				|  |  |  		var res map[string]interface{}
 | 
	
	
		
			
				|  | @@ -183,10 +190,12 @@ func parse(payload []byte, getter func() interface{}) (interface{}, error) {
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  		return res, nil
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	typed := getter()
 | 
	
		
			
				|  |  |  	if err := json.Unmarshal(payload, &typed); err != nil {
 | 
	
		
			
				|  |  |  		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	return reflect.ValueOf(typed).Elem().Interface(), nil
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -204,7 +213,7 @@ func (s *store) Create(name string, data TypedContext) error {
 | 
	
		
			
				|  |  |  	if name == DefaultContextName {
 | 
	
		
			
				|  |  |  		return errors.Wrap(errdefs.ErrAlreadyExists, objectName(name))
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	dir := contextdirOf(name)
 | 
	
		
			
				|  |  | +	dir := contextDirOf(name)
 | 
	
		
			
				|  |  |  	metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir)
 | 
	
		
			
				|  |  |  	if _, err := os.Stat(metaDir); !os.IsNotExist(err) {
 | 
	
		
			
				|  |  |  		return errors.Wrap(errdefs.ErrAlreadyExists, objectName(name))
 | 
	
	
		
			
				|  | @@ -222,9 +231,9 @@ func (s *store) Create(name string, data TypedContext) error {
 | 
	
		
			
				|  |  |  	meta := Metadata{
 | 
	
		
			
				|  |  |  		Name:     name,
 | 
	
		
			
				|  |  |  		Metadata: data,
 | 
	
		
			
				|  |  | -		Endpoints: map[string]interface{}{
 | 
	
		
			
				|  |  | -			(dockerEndpointKey): dummyContext{},
 | 
	
		
			
				|  |  | -			(data.Type):         dummyContext{},
 | 
	
		
			
				|  |  | +		Endpoints: map[string]Endpoint{
 | 
	
		
			
				|  |  | +			(dockerEndpointKey): {},
 | 
	
		
			
				|  |  | +			(data.Type):         {},
 | 
	
		
			
				|  |  |  		},
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -255,6 +264,12 @@ func (s *store) List() ([]*Metadata, error) {
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	dockerDefault, err := dockerGefaultContext()
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	result = append(result, dockerDefault)
 | 
	
		
			
				|  |  |  	return result, nil
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -262,7 +277,7 @@ func (s *store) Remove(name string) error {
 | 
	
		
			
				|  |  |  	if name == DefaultContextName {
 | 
	
		
			
				|  |  |  		return errors.Wrap(errdefs.ErrForbidden, objectName(name))
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	dir := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name))
 | 
	
		
			
				|  |  | +	dir := filepath.Join(s.root, contextsDir, metadataDir, contextDirOf(name))
 | 
	
		
			
				|  |  |  	// Check if directory exists because os.RemoveAll returns nil if it doesn't
 | 
	
		
			
				|  |  |  	if _, err := os.Stat(dir); os.IsNotExist(err) {
 | 
	
		
			
				|  |  |  		return errors.Wrap(errdefs.ErrNotFound, objectName(name))
 | 
	
	
		
			
				|  | @@ -273,7 +288,7 @@ func (s *store) Remove(name string) error {
 | 
	
		
			
				|  |  |  	return nil
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -func contextdirOf(name string) string {
 | 
	
		
			
				|  |  | +func contextDirOf(name string) string {
 | 
	
		
			
				|  |  |  	return digest.FromString(name).Encoded()
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -281,32 +296,49 @@ func objectName(name string) string {
 | 
	
		
			
				|  |  |  	return fmt.Sprintf("context %q", name)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +func createDirIfNotExist(dir string) error {
 | 
	
		
			
				|  |  | +	if _, err := os.Stat(dir); os.IsNotExist(err) {
 | 
	
		
			
				|  |  | +		if err = os.MkdirAll(dir, 0755); err != nil {
 | 
	
		
			
				|  |  | +			return err
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	return nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  type dummyContext struct{}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// Endpoint holds the Docker or the Kubernetes endpoint
 | 
	
		
			
				|  |  | +type Endpoint struct {
 | 
	
		
			
				|  |  | +	Host             string `json:",omitempty"`
 | 
	
		
			
				|  |  | +	DefaultNamespace string `json:",omitempty"`
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // Metadata represents the docker context metadata
 | 
	
		
			
				|  |  |  type Metadata struct {
 | 
	
		
			
				|  |  | -	Name      string                 `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Metadata  TypedContext           `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Endpoints map[string]interface{} `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Name      string              `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Metadata  TypedContext        `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Endpoints map[string]Endpoint `json:",omitempty"`
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  type untypedMetadata struct {
 | 
	
		
			
				|  |  | -	Name      string                 `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Metadata  json.RawMessage        `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Endpoints map[string]interface{} `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Name      string              `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Metadata  json.RawMessage     `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Endpoints map[string]Endpoint `json:",omitempty"`
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  type untypedContext struct {
 | 
	
		
			
				|  |  | -	Data        json.RawMessage `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Description string          `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Type        string          `json:",omitempty"`
 | 
	
		
			
				|  |  | +	StackOrchestrator string          `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Type              string          `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Description       string          `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Data              json.RawMessage `json:",omitempty"`
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // TypedContext is a context with a type (moby, aci, etc...)
 | 
	
		
			
				|  |  |  type TypedContext struct {
 | 
	
		
			
				|  |  | -	Type        string      `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Description string      `json:",omitempty"`
 | 
	
		
			
				|  |  | -	Data        interface{} `json:",omitempty"`
 | 
	
		
			
				|  |  | +	StackOrchestrator string      `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Type              string      `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Description       string      `json:",omitempty"`
 | 
	
		
			
				|  |  | +	Data              interface{} `json:",omitempty"`
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // AciContext is the context for ACI
 |