store.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. Copyright (c) 2020 Docker Inc.
  3. Permission is hereby granted, free of charge, to any person
  4. obtaining a copy of this software and associated documentation
  5. files (the "Software"), to deal in the Software without
  6. restriction, including without limitation the rights to use, copy,
  7. modify, merge, publish, distribute, sublicense, and/or sell copies
  8. of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be
  11. included in all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  13. EXPRESS OR IMPLIED,
  14. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  16. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  17. HOLDERS BE LIABLE FOR ANY CLAIM,
  18. DAMAGES OR OTHER LIABILITY,
  19. WHETHER IN AN ACTION OF CONTRACT,
  20. TORT OR OTHERWISE,
  21. ARISING FROM, OUT OF OR IN CONNECTION WITH
  22. THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. */
  24. package store
  25. import (
  26. "context"
  27. "encoding/json"
  28. "fmt"
  29. "io/ioutil"
  30. "os"
  31. "path/filepath"
  32. "reflect"
  33. "github.com/opencontainers/go-digest"
  34. )
  35. const (
  36. contextsDir = "contexts"
  37. metadataDir = "meta"
  38. metaFile = "meta.json"
  39. )
  40. type contextStoreKey struct{}
  41. // WithContextStore adds the store to the context
  42. func WithContextStore(ctx context.Context, store Store) context.Context {
  43. return context.WithValue(ctx, contextStoreKey{}, store)
  44. }
  45. // ContextStore returns the store from the context
  46. func ContextStore(ctx context.Context) Store {
  47. s, _ := ctx.Value(contextStoreKey{}).(Store)
  48. return s
  49. }
  50. // Store is the context store
  51. type Store interface {
  52. // Get returns the context with name, it returns an error if the context
  53. // doesn't exist
  54. Get(name string, getter func() interface{}) (*Metadata, error)
  55. // GetType reurns the type of the context (docker, aci etc)
  56. GetType(meta *Metadata) string
  57. // Create creates a new context, it returns an error if a context with the
  58. // same name exists already.
  59. Create(name string, data TypedContext) error
  60. // List returns the list of created contexts
  61. List() ([]*Metadata, error)
  62. }
  63. type store struct {
  64. root string
  65. }
  66. // Opt is a functional option for the store
  67. type Opt func(*store)
  68. // WithRoot sets a new root to the store
  69. func WithRoot(root string) Opt {
  70. return func(s *store) {
  71. s.root = root
  72. }
  73. }
  74. // New returns a configured context store with $HOME/.docker as root
  75. func New(opts ...Opt) (Store, error) {
  76. home, err := os.UserHomeDir()
  77. if err != nil {
  78. return nil, err
  79. }
  80. s := &store{
  81. root: filepath.Join(home, ".docker"),
  82. }
  83. for _, opt := range opts {
  84. opt(s)
  85. }
  86. cd := filepath.Join(s.root, contextsDir)
  87. if _, err := os.Stat(cd); os.IsNotExist(err) {
  88. if err = os.Mkdir(cd, 0755); err != nil {
  89. return nil, err
  90. }
  91. }
  92. m := filepath.Join(cd, metadataDir)
  93. if _, err := os.Stat(m); os.IsNotExist(err) {
  94. if err = os.Mkdir(m, 0755); err != nil {
  95. return nil, err
  96. }
  97. }
  98. return s, nil
  99. }
  100. // Get returns the context with the given name
  101. func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
  102. meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile)
  103. m, err := read(meta, getter)
  104. if os.IsNotExist(err) {
  105. return nil, fmt.Errorf("unknown context %q", name)
  106. } else if err != nil {
  107. return nil, err
  108. }
  109. return m, nil
  110. }
  111. func read(meta string, getter func() interface{}) (*Metadata, error) {
  112. bytes, err := ioutil.ReadFile(meta)
  113. if err != nil {
  114. return nil, err
  115. }
  116. var um untypedMetadata
  117. if err := json.Unmarshal(bytes, &um); err != nil {
  118. return nil, err
  119. }
  120. var uc untypedContext
  121. if err := json.Unmarshal(um.Metadata, &uc); err != nil {
  122. return nil, err
  123. }
  124. data, err := parse(uc.Data, getter)
  125. if err != nil {
  126. return nil, err
  127. }
  128. return &Metadata{
  129. Name: um.Name,
  130. Endpoints: um.Endpoints,
  131. Metadata: TypedContext{
  132. Description: uc.Description,
  133. Type: uc.Type,
  134. Data: data,
  135. },
  136. }, nil
  137. }
  138. func parse(payload []byte, getter func() interface{}) (interface{}, error) {
  139. if getter == nil {
  140. var res map[string]interface{}
  141. if err := json.Unmarshal(payload, &res); err != nil {
  142. return nil, err
  143. }
  144. return res, nil
  145. }
  146. typed := getter()
  147. if err := json.Unmarshal(payload, &typed); err != nil {
  148. return nil, err
  149. }
  150. return reflect.ValueOf(typed).Elem().Interface(), nil
  151. }
  152. func (s *store) GetType(meta *Metadata) string {
  153. for k := range meta.Endpoints {
  154. if k != "docker" {
  155. return k
  156. }
  157. }
  158. return "docker"
  159. }
  160. func (s *store) Create(name string, data TypedContext) error {
  161. dir := contextdirOf(name)
  162. metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir)
  163. if _, err := os.Stat(metaDir); !os.IsNotExist(err) {
  164. return fmt.Errorf("context %q already exists", name)
  165. }
  166. err := os.Mkdir(metaDir, 0755)
  167. if err != nil {
  168. return err
  169. }
  170. if data.Data == nil {
  171. data.Data = dummyContext{}
  172. }
  173. meta := Metadata{
  174. Name: name,
  175. Metadata: data,
  176. Endpoints: map[string]interface{}{
  177. "docker": dummyContext{},
  178. (data.Type): dummyContext{},
  179. },
  180. }
  181. bytes, err := json.Marshal(&meta)
  182. if err != nil {
  183. return err
  184. }
  185. return ioutil.WriteFile(filepath.Join(metaDir, metaFile), bytes, 0644)
  186. }
  187. func (s *store) List() ([]*Metadata, error) {
  188. root := filepath.Join(s.root, contextsDir, metadataDir)
  189. c, err := ioutil.ReadDir(root)
  190. if err != nil {
  191. return nil, err
  192. }
  193. var result []*Metadata
  194. for _, fi := range c {
  195. if fi.IsDir() {
  196. meta := filepath.Join(root, fi.Name(), metaFile)
  197. r, err := read(meta, nil)
  198. if err != nil {
  199. return nil, err
  200. }
  201. result = append(result, r)
  202. }
  203. }
  204. return result, nil
  205. }
  206. func contextdirOf(name string) string {
  207. return digest.FromString(name).Encoded()
  208. }
  209. type dummyContext struct{}
  210. // Metadata represents the docker context metadata
  211. type Metadata struct {
  212. Name string `json:",omitempty"`
  213. Metadata TypedContext `json:",omitempty"`
  214. Endpoints map[string]interface{} `json:",omitempty"`
  215. }
  216. type untypedMetadata struct {
  217. Name string `json:",omitempty"`
  218. Metadata json.RawMessage `json:",omitempty"`
  219. Endpoints map[string]interface{} `json:",omitempty"`
  220. }
  221. type untypedContext struct {
  222. Data json.RawMessage `json:",omitempty"`
  223. Description string `json:",omitempty"`
  224. Type string `json:",omitempty"`
  225. }
  226. // TypedContext is a context with a type (moby, aci, etc...)
  227. type TypedContext struct {
  228. Type string `json:",omitempty"`
  229. Description string `json:",omitempty"`
  230. Data interface{} `json:",omitempty"`
  231. }
  232. // AciContext is the context for ACI
  233. type AciContext struct {
  234. SubscriptionID string `json:",omitempty"`
  235. Location string `json:",omitempty"`
  236. ResourceGroup string `json:",omitempty"`
  237. }