store.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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. func WithContextStore(ctx context.Context, store Store) context.Context {
  42. return context.WithValue(ctx, contextStoreKey{}, store)
  43. }
  44. func ContextStore(ctx context.Context) Store {
  45. s, _ := ctx.Value(contextStoreKey{}).(Store)
  46. return s
  47. }
  48. // Store
  49. type Store interface {
  50. // Get returns the context with name, it returns an error if the context
  51. // doesn't exist
  52. Get(name string, getter func() interface{}) (*Metadata, error)
  53. // GetType reurns the type of the context (docker, aci etc)
  54. GetType(meta *Metadata) string
  55. // Create creates a new context, it returns an error if a context with the
  56. // same name exists already.
  57. Create(name string, data TypedContext) error
  58. // List returns the list of created contexts
  59. List() ([]*Metadata, error)
  60. }
  61. type store struct {
  62. root string
  63. }
  64. type StoreOpt func(*store)
  65. func WithRoot(root string) StoreOpt {
  66. return func(s *store) {
  67. s.root = root
  68. }
  69. }
  70. // New returns a configured context store
  71. func New(opts ...StoreOpt) (Store, error) {
  72. home, err := os.UserHomeDir()
  73. if err != nil {
  74. return nil, err
  75. }
  76. s := &store{
  77. root: filepath.Join(home, ".docker"),
  78. }
  79. for _, opt := range opts {
  80. opt(s)
  81. }
  82. cd := filepath.Join(s.root, contextsDir)
  83. if _, err := os.Stat(cd); os.IsNotExist(err) {
  84. if err = os.Mkdir(cd, 0755); err != nil {
  85. return nil, err
  86. }
  87. }
  88. m := filepath.Join(cd, metadataDir)
  89. if _, err := os.Stat(m); os.IsNotExist(err) {
  90. if err = os.Mkdir(m, 0755); err != nil {
  91. return nil, err
  92. }
  93. }
  94. return s, nil
  95. }
  96. // Get returns the context with the given name
  97. func (s *store) Get(name string, getter func() interface{}) (*Metadata, error) {
  98. meta := filepath.Join(s.root, contextsDir, metadataDir, contextdirOf(name), metaFile)
  99. return read(meta, getter)
  100. }
  101. func read(meta string, getter func() interface{}) (*Metadata, error) {
  102. bytes, err := ioutil.ReadFile(meta)
  103. if err != nil {
  104. return nil, err
  105. }
  106. var um untypedMetadata
  107. if err := json.Unmarshal(bytes, &um); err != nil {
  108. return nil, err
  109. }
  110. var uc untypedContext
  111. if err := json.Unmarshal(um.Metadata, &uc); err != nil {
  112. return nil, err
  113. }
  114. data, err := parse(uc.Data, getter)
  115. if err != nil {
  116. return nil, err
  117. }
  118. return &Metadata{
  119. Name: um.Name,
  120. Endpoints: um.Endpoints,
  121. Metadata: TypedContext{
  122. Description: uc.Description,
  123. Type: uc.Type,
  124. Data: data,
  125. },
  126. }, nil
  127. }
  128. func parse(payload []byte, getter func() interface{}) (interface{}, error) {
  129. if getter == nil {
  130. var res map[string]interface{}
  131. if err := json.Unmarshal(payload, &res); err != nil {
  132. return nil, err
  133. }
  134. return res, nil
  135. }
  136. typed := getter()
  137. if err := json.Unmarshal(payload, &typed); err != nil {
  138. return nil, err
  139. }
  140. return reflect.ValueOf(typed).Elem().Interface(), nil
  141. }
  142. func (s *store) GetType(meta *Metadata) string {
  143. for k := range meta.Endpoints {
  144. if k != "docker" {
  145. return k
  146. }
  147. }
  148. return "docker"
  149. }
  150. func (s *store) Create(name string, data TypedContext) error {
  151. dir := contextdirOf(name)
  152. metaDir := filepath.Join(s.root, contextsDir, metadataDir, dir)
  153. if _, err := os.Stat(metaDir); !os.IsNotExist(err) {
  154. return fmt.Errorf("Context %q already exists", name)
  155. }
  156. err := os.Mkdir(metaDir, 0755)
  157. if err != nil {
  158. return err
  159. }
  160. if data.Data == nil {
  161. data.Data = DummyContext{}
  162. }
  163. meta := Metadata{
  164. Name: name,
  165. Metadata: data,
  166. Endpoints: map[string]interface{}{
  167. "docker": DummyContext{},
  168. (data.Type): DummyContext{},
  169. },
  170. }
  171. bytes, err := json.Marshal(&meta)
  172. if err != nil {
  173. return err
  174. }
  175. return ioutil.WriteFile(filepath.Join(metaDir, metaFile), bytes, 0644)
  176. }
  177. func (s *store) List() ([]*Metadata, error) {
  178. root := filepath.Join(s.root, contextsDir, metadataDir)
  179. c, err := ioutil.ReadDir(root)
  180. if err != nil {
  181. return nil, err
  182. }
  183. var result []*Metadata
  184. for _, fi := range c {
  185. if fi.IsDir() {
  186. meta := filepath.Join(root, fi.Name(), metaFile)
  187. r, err := read(meta, nil)
  188. if err != nil {
  189. return nil, err
  190. }
  191. result = append(result, r)
  192. }
  193. }
  194. return result, nil
  195. }
  196. func contextdirOf(name string) string {
  197. return digest.FromString(name).Encoded()
  198. }
  199. type DummyContext struct{}
  200. type Metadata struct {
  201. Name string `json:",omitempty"`
  202. Metadata TypedContext `json:",omitempty"`
  203. Endpoints map[string]interface{} `json:",omitempty"`
  204. }
  205. type untypedMetadata struct {
  206. Name string `json:",omitempty"`
  207. Metadata json.RawMessage `json:",omitempty"`
  208. Endpoints map[string]interface{} `json:",omitempty"`
  209. }
  210. type untypedContext struct {
  211. Data json.RawMessage `json:",omitempty"`
  212. Description string `json:",omitempty"`
  213. Type string `json:",omitempty"`
  214. }
  215. type TypedContext struct {
  216. Type string `json:",omitempty"`
  217. Description string `json:",omitempty"`
  218. Data interface{} `json:",omitempty"`
  219. }
  220. type AciContext struct {
  221. SubscriptionID string `json:",omitempty"`
  222. Location string `json:",omitempty"`
  223. ResourceGroup string `json:",omitempty"`
  224. }