store.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. "github.com/pkg/errors"
  35. "github.com/docker/api/errdefs"
  36. )
  37. const (
  38. // DefaultContextName is an automatically generated local context
  39. DefaultContextName = "default"
  40. )
  41. const (
  42. contextsDir = "contexts"
  43. metadataDir = "meta"
  44. metaFile = "meta.json"
  45. )
  46. type contextStoreKey struct{}
  47. // WithContextStore adds the store to the context
  48. func WithContextStore(ctx context.Context, store Store) context.Context {
  49. return context.WithValue(ctx, contextStoreKey{}, store)
  50. }
  51. // ContextStore returns the store from the context
  52. func ContextStore(ctx context.Context) Store {
  53. s, _ := ctx.Value(contextStoreKey{}).(Store)
  54. return s
  55. }
  56. // Store is the context store
  57. type Store interface {
  58. // Get returns the context with name, it returns an error if the context
  59. // doesn't exist
  60. Get(name string, getter func() interface{}) (*Metadata, error)
  61. // GetType returns the type of the context (docker, aci etc)
  62. GetType(meta *Metadata) string
  63. // Create creates a new context, it returns an error if a context with the
  64. // same name exists already.
  65. Create(name string, data TypedContext) error
  66. // List returns the list of created contexts
  67. List() ([]*Metadata, error)
  68. // Remove removes a context by name from the context store
  69. Remove(name string) error
  70. }
  71. type store struct {
  72. root string
  73. }
  74. // Opt is a functional option for the store
  75. type Opt func(*store)
  76. // WithRoot sets a new root to the store
  77. func WithRoot(root string) Opt {
  78. return func(s *store) {
  79. s.root = root
  80. }
  81. }
  82. // New returns a configured context store with $HOME/.docker as root
  83. func New(opts ...Opt) (Store, error) {
  84. home, err := os.UserHomeDir()
  85. if err != nil {
  86. return nil, err
  87. }
  88. s := &store{
  89. root: filepath.Join(home, ".docker"),
  90. }
  91. if _, err := os.Stat(s.root); os.IsNotExist(err) {
  92. if err = os.Mkdir(s.root, 0755); err != nil {
  93. return nil, err
  94. }
  95. }
  96. for _, opt := range opts {
  97. opt(s)
  98. }
  99. cd := filepath.Join(s.root, contextsDir)
  100. if _, err := os.Stat(cd); os.IsNotExist(err) {
  101. if err = os.Mkdir(cd, 0755); err != nil {
  102. return nil, err
  103. }
  104. }
  105. m := filepath.Join(cd, metadataDir)
  106. if _, err := os.Stat(m); os.IsNotExist(err) {
  107. if err = os.Mkdir(m, 0755); err != nil {
  108. return nil, err
  109. }
  110. }
  111. return s, nil
  112. }
  113. // Get returns the context with the given name
  114. func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
  115. meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile)
  116. m, err := read(meta, getter)
  117. if os.IsNotExist(err) {
  118. return nil, errors.Wrap(errdefs.ErrNotFound, objectName(name))
  119. } else if err != nil {
  120. return nil, err
  121. }
  122. return m, nil
  123. }
  124. func read(meta string, getter func() interface{}) (*Metadata, error) {
  125. bytes, err := ioutil.ReadFile(meta)
  126. if err != nil {
  127. return nil, err
  128. }
  129. var um untypedMetadata
  130. if err := json.Unmarshal(bytes, &um); err != nil {
  131. return nil, err
  132. }
  133. var uc untypedContext
  134. if err := json.Unmarshal(um.Metadata, &uc); err != nil {
  135. return nil, err
  136. }
  137. data, err := parse(uc.Data, getter)
  138. if err != nil {
  139. return nil, err
  140. }
  141. return &Metadata{
  142. Name: um.Name,
  143. Endpoints: um.Endpoints,
  144. Metadata: TypedContext{
  145. Description: uc.Description,
  146. Type: uc.Type,
  147. Data: data,
  148. },
  149. }, nil
  150. }
  151. func parse(payload []byte, getter func() interface{}) (interface{}, error) {
  152. if getter == nil {
  153. var res map[string]interface{}
  154. if err := json.Unmarshal(payload, &res); err != nil {
  155. return nil, err
  156. }
  157. return res, nil
  158. }
  159. typed := getter()
  160. if err := json.Unmarshal(payload, &typed); err != nil {
  161. return nil, err
  162. }
  163. return reflect.ValueOf(typed).Elem().Interface(), nil
  164. }
  165. func (s *store) GetType(meta *Metadata) string {
  166. for k := range meta.Endpoints {
  167. if k != "docker" {
  168. return k
  169. }
  170. }
  171. return "docker"
  172. }
  173. func (s *store) Create(name string, data TypedContext) error {
  174. if name == DefaultContextName {
  175. return errors.Wrap(errdefs.ErrAlreadyExists, objectName(name))
  176. }
  177. dir := contextdirOf(name)
  178. metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir)
  179. if _, err := os.Stat(metaDir); !os.IsNotExist(err) {
  180. return errors.Wrap(errdefs.ErrAlreadyExists, objectName(name))
  181. }
  182. err := os.Mkdir(metaDir, 0755)
  183. if err != nil {
  184. return err
  185. }
  186. if data.Data == nil {
  187. data.Data = dummyContext{}
  188. }
  189. meta := Metadata{
  190. Name: name,
  191. Metadata: data,
  192. Endpoints: map[string]interface{}{
  193. "docker": dummyContext{},
  194. (data.Type): dummyContext{},
  195. },
  196. }
  197. bytes, err := json.Marshal(&meta)
  198. if err != nil {
  199. return err
  200. }
  201. return ioutil.WriteFile(filepath.Join(metaDir, metaFile), bytes, 0644)
  202. }
  203. func (s *store) List() ([]*Metadata, error) {
  204. root := filepath.Join(s.root, contextsDir, metadataDir)
  205. c, err := ioutil.ReadDir(root)
  206. if err != nil {
  207. return nil, err
  208. }
  209. var result []*Metadata
  210. for _, fi := range c {
  211. if fi.IsDir() {
  212. meta := filepath.Join(root, fi.Name(), metaFile)
  213. r, err := read(meta, nil)
  214. if err != nil {
  215. return nil, err
  216. }
  217. result = append(result, r)
  218. }
  219. }
  220. return result, nil
  221. }
  222. func (s *store) Remove(name string) error {
  223. if name == DefaultContextName {
  224. return errors.Wrap(errdefs.ErrForbidden, objectName(name))
  225. }
  226. dir := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name))
  227. // Check if directory exists because os.RemoveAll returns nil if it doesn't
  228. if _, err := os.Stat(dir); os.IsNotExist(err) {
  229. return errors.Wrap(errdefs.ErrNotFound, objectName(name))
  230. }
  231. if err := os.RemoveAll(dir); err != nil {
  232. return errors.Wrapf(errdefs.ErrUnknown, "unable to remove %s: %s", objectName(name), err)
  233. }
  234. return nil
  235. }
  236. func contextdirOf(name string) string {
  237. return digest.FromString(name).Encoded()
  238. }
  239. func objectName(name string) string {
  240. return fmt.Sprintf("context %q", name)
  241. }
  242. type dummyContext struct{}
  243. // Metadata represents the docker context metadata
  244. type Metadata struct {
  245. Name string `json:",omitempty"`
  246. Metadata TypedContext `json:",omitempty"`
  247. Endpoints map[string]interface{} `json:",omitempty"`
  248. }
  249. type untypedMetadata struct {
  250. Name string `json:",omitempty"`
  251. Metadata json.RawMessage `json:",omitempty"`
  252. Endpoints map[string]interface{} `json:",omitempty"`
  253. }
  254. type untypedContext struct {
  255. Data json.RawMessage `json:",omitempty"`
  256. Description string `json:",omitempty"`
  257. Type string `json:",omitempty"`
  258. }
  259. // TypedContext is a context with a type (moby, aci, etc...)
  260. type TypedContext struct {
  261. Type string `json:",omitempty"`
  262. Description string `json:",omitempty"`
  263. Data interface{} `json:",omitempty"`
  264. }
  265. // AciContext is the context for ACI
  266. type AciContext struct {
  267. SubscriptionID string `json:",omitempty"`
  268. Location string `json:",omitempty"`
  269. ResourceGroup string `json:",omitempty"`
  270. }