|
|
@@ -72,18 +72,66 @@ func ContextStore(ctx context.Context) Store {
|
|
|
type Store interface {
|
|
|
// Get returns the context with name, it returns an error if the context
|
|
|
// doesn't exist
|
|
|
- Get(name string, getter func() interface{}) (*Metadata, error)
|
|
|
- // GetType returns the type of the context (docker, aci etc)
|
|
|
- GetType(meta *Metadata) string
|
|
|
+ Get(name string) (*Metadata, error)
|
|
|
+ // GetEndpoint sets the `v` parameter to the value of the endpoint for a
|
|
|
+ // particular context type
|
|
|
+ GetEndpoint(name string, v interface{}) error
|
|
|
// Create creates a new context, it returns an error if a context with the
|
|
|
// same name exists already.
|
|
|
- Create(name string, data TypedContext) error
|
|
|
+ Create(name string, contextType string, description string, data interface{}) error
|
|
|
// List returns the list of created contexts
|
|
|
List() ([]*Metadata, error)
|
|
|
// Remove removes a context by name from the context store
|
|
|
Remove(name string) error
|
|
|
}
|
|
|
|
|
|
+// Endpoint holds the Docker or the Kubernetes endpoint, they both have the
|
|
|
+// `Host` property, only kubernetes will have the `DefaultNamespace`
|
|
|
+type Endpoint struct {
|
|
|
+ Host string `json:",omitempty"`
|
|
|
+ DefaultNamespace string `json:",omitempty"`
|
|
|
+}
|
|
|
+
|
|
|
+const (
|
|
|
+ // AciContextType is the endpoint key in the context endpoints for an ACI
|
|
|
+ // backend
|
|
|
+ AciContextType = "aci"
|
|
|
+ // MobyContextType is the endpoint key in the context endpoints for a moby
|
|
|
+ // backend
|
|
|
+ MobyContextType = "moby"
|
|
|
+ // ExampleContextType is the endpoint key in the context endpoints for an
|
|
|
+ // example backend
|
|
|
+ ExampleContextType = "example"
|
|
|
+)
|
|
|
+
|
|
|
+// Metadata represents the docker context metadata
|
|
|
+type Metadata struct {
|
|
|
+ Name string `json:",omitempty"`
|
|
|
+ Type string `json:",omitempty"`
|
|
|
+ Metadata ContextMetadata `json:",omitempty"`
|
|
|
+ Endpoints map[string]interface{} `json:",omitempty"`
|
|
|
+}
|
|
|
+
|
|
|
+// ContextMetadata is represtentation of the data we put in a context
|
|
|
+// metadata
|
|
|
+type ContextMetadata struct {
|
|
|
+ Description string `json:",omitempty"`
|
|
|
+ StackOrchestrator string `json:",omitempty"`
|
|
|
+}
|
|
|
+
|
|
|
+// AciContext is the context for the ACI backend
|
|
|
+type AciContext struct {
|
|
|
+ SubscriptionID string `json:",omitempty"`
|
|
|
+ Location string `json:",omitempty"`
|
|
|
+ ResourceGroup string `json:",omitempty"`
|
|
|
+}
|
|
|
+
|
|
|
+// MobyContext is the context for the moby backend
|
|
|
+type MobyContext struct{}
|
|
|
+
|
|
|
+// ExampleContext is the context for the example backend
|
|
|
+type ExampleContext struct{}
|
|
|
+
|
|
|
type store struct {
|
|
|
root string
|
|
|
}
|
|
|
@@ -127,9 +175,9 @@ func New(opts ...Opt) (Store, error) {
|
|
|
}
|
|
|
|
|
|
// Get returns the context with the given name
|
|
|
-func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
|
|
|
+func (s *store) Get(name string) (*Metadata, error) {
|
|
|
meta := filepath.Join(s.root, contextsDir, metadataDir, contextDirOf(name), metaFile)
|
|
|
- m, err := read(meta, getter)
|
|
|
+ m, err := read(meta)
|
|
|
if os.IsNotExist(err) {
|
|
|
return nil, errors.Wrap(errdefs.ErrNotFound, objectName(name))
|
|
|
} else if err != nil {
|
|
|
@@ -139,73 +187,75 @@ func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
|
|
|
return m, nil
|
|
|
}
|
|
|
|
|
|
-func read(meta string, getter func() interface{}) (*Metadata, error) {
|
|
|
+func (s *store) GetEndpoint(name string, data interface{}) error {
|
|
|
+ meta, err := s.Get(name)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if _, ok := meta.Endpoints[meta.Type]; !ok {
|
|
|
+ return errors.Wrapf(errdefs.ErrNotFound, "endpoint of type %q", meta.Type)
|
|
|
+ }
|
|
|
+
|
|
|
+ dstPtrValue := reflect.ValueOf(data)
|
|
|
+ dstValue := reflect.Indirect(dstPtrValue)
|
|
|
+
|
|
|
+ val := reflect.ValueOf(meta.Endpoints[meta.Type])
|
|
|
+ valIndirect := reflect.Indirect(val)
|
|
|
+
|
|
|
+ if dstValue.Type() != valIndirect.Type() {
|
|
|
+ return errdefs.ErrWrongContextType
|
|
|
+ }
|
|
|
+
|
|
|
+ dstValue.Set(valIndirect)
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func read(meta string) (*Metadata, error) {
|
|
|
bytes, err := ioutil.ReadFile(meta)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
- var um untypedMetadata
|
|
|
- if err := json.Unmarshal(bytes, &um); err != nil {
|
|
|
+ var metadata Metadata
|
|
|
+ if err := json.Unmarshal(bytes, &metadata); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
- var uc untypedContext
|
|
|
- if err := json.Unmarshal(um.Metadata, &uc); err != nil {
|
|
|
+ metadata.Endpoints, err = toTypedEndpoints(metadata.Endpoints)
|
|
|
+ if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
- if uc.Type == "" {
|
|
|
- uc.Type = "docker"
|
|
|
- }
|
|
|
|
|
|
- var data interface{}
|
|
|
- if uc.Data != nil {
|
|
|
- data, err = parse(uc.Data, getter)
|
|
|
+ return &metadata, nil
|
|
|
+}
|
|
|
+
|
|
|
+func toTypedEndpoints(endpoints map[string]interface{}) (map[string]interface{}, error) {
|
|
|
+ result := map[string]interface{}{}
|
|
|
+ for k, v := range endpoints {
|
|
|
+ bytes, err := json.Marshal(v)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- return &Metadata{
|
|
|
- Name: um.Name,
|
|
|
- Endpoints: um.Endpoints,
|
|
|
- Metadata: TypedContext{
|
|
|
- StackOrchestrator: uc.StackOrchestrator,
|
|
|
- Description: uc.Description,
|
|
|
- Type: uc.Type,
|
|
|
- Data: data,
|
|
|
- },
|
|
|
- }, nil
|
|
|
-}
|
|
|
+ typeGetters := getters()
|
|
|
+ if _, ok := typeGetters[k]; !ok {
|
|
|
+ result[k] = v
|
|
|
+ continue
|
|
|
+ }
|
|
|
|
|
|
-func parse(payload []byte, getter func() interface{}) (interface{}, error) {
|
|
|
- if getter == nil {
|
|
|
- var res map[string]interface{}
|
|
|
- if err := json.Unmarshal(payload, &res); err != nil {
|
|
|
+ val := typeGetters[k]()
|
|
|
+ err = json.Unmarshal(bytes, &val)
|
|
|
+ if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
- return res, nil
|
|
|
- }
|
|
|
-
|
|
|
- typed := getter()
|
|
|
- if err := json.Unmarshal(payload, &typed); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- return reflect.ValueOf(typed).Elem().Interface(), nil
|
|
|
-}
|
|
|
|
|
|
-func (s *store) GetType(meta *Metadata) string {
|
|
|
- for k := range meta.Endpoints {
|
|
|
- if k != dockerEndpointKey {
|
|
|
- return k
|
|
|
- }
|
|
|
+ result[k] = val
|
|
|
}
|
|
|
|
|
|
- return dockerEndpointKey
|
|
|
+ return result, nil
|
|
|
}
|
|
|
|
|
|
-func (s *store) Create(name string, data TypedContext) error {
|
|
|
+func (s *store) Create(name string, contextType string, description string, data interface{}) error {
|
|
|
if name == DefaultContextName {
|
|
|
return errors.Wrap(errdefs.ErrAlreadyExists, objectName(name))
|
|
|
}
|
|
|
@@ -220,16 +270,15 @@ func (s *store) Create(name string, data TypedContext) error {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if data.Data == nil {
|
|
|
- data.Data = dummyContext{}
|
|
|
- }
|
|
|
-
|
|
|
meta := Metadata{
|
|
|
- Name: name,
|
|
|
- Metadata: data,
|
|
|
- Endpoints: map[string]Endpoint{
|
|
|
- (dockerEndpointKey): {},
|
|
|
- (data.Type): {},
|
|
|
+ Name: name,
|
|
|
+ Type: contextType,
|
|
|
+ Metadata: ContextMetadata{
|
|
|
+ Description: description,
|
|
|
+ },
|
|
|
+ Endpoints: map[string]interface{}{
|
|
|
+ (dockerEndpointKey): data,
|
|
|
+ (contextType): data,
|
|
|
},
|
|
|
}
|
|
|
|
|
|
@@ -252,7 +301,7 @@ func (s *store) List() ([]*Metadata, error) {
|
|
|
for _, fi := range c {
|
|
|
if fi.IsDir() {
|
|
|
meta := filepath.Join(root, fi.Name(), metaFile)
|
|
|
- r, err := read(meta, nil)
|
|
|
+ r, err := read(meta)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
@@ -303,45 +352,19 @@ func createDirIfNotExist(dir string) error {
|
|
|
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]Endpoint `json:",omitempty"`
|
|
|
-}
|
|
|
-
|
|
|
-type untypedMetadata struct {
|
|
|
- Name string `json:",omitempty"`
|
|
|
- Metadata json.RawMessage `json:",omitempty"`
|
|
|
- Endpoints map[string]Endpoint `json:",omitempty"`
|
|
|
-}
|
|
|
-
|
|
|
-type untypedContext struct {
|
|
|
- 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 {
|
|
|
- StackOrchestrator string `json:",omitempty"`
|
|
|
- Type string `json:",omitempty"`
|
|
|
- Description string `json:",omitempty"`
|
|
|
- Data interface{} `json:",omitempty"`
|
|
|
-}
|
|
|
-
|
|
|
-// AciContext is the context for ACI
|
|
|
-type AciContext struct {
|
|
|
- SubscriptionID string `json:",omitempty"`
|
|
|
- Location string `json:",omitempty"`
|
|
|
- ResourceGroup string `json:",omitempty"`
|
|
|
+// Different context types managed by the store.
|
|
|
+// TODO(rumpl): we should make this extensible in the future if we want to
|
|
|
+// be able to manage other contexts.
|
|
|
+func getters() map[string]func() interface{} {
|
|
|
+ return map[string]func() interface{}{
|
|
|
+ "aci": func() interface{} {
|
|
|
+ return &AciContext{}
|
|
|
+ },
|
|
|
+ "moby": func() interface{} {
|
|
|
+ return &MobyContext{}
|
|
|
+ },
|
|
|
+ "example": func() interface{} {
|
|
|
+ return &ExampleContext{}
|
|
|
+ },
|
|
|
+ }
|
|
|
}
|