commands_runmount.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // +build dfrunmount
  2. package instructions
  3. import (
  4. "encoding/csv"
  5. "strconv"
  6. "strings"
  7. "github.com/pkg/errors"
  8. )
  9. const MountTypeBind = "bind"
  10. const MountTypeCache = "cache"
  11. const MountTypeTmpfs = "tmpfs"
  12. const MountTypeSecret = "secret"
  13. const MountTypeSSH = "ssh"
  14. var allowedMountTypes = map[string]struct{}{
  15. MountTypeBind: {},
  16. MountTypeCache: {},
  17. MountTypeTmpfs: {},
  18. MountTypeSecret: {},
  19. MountTypeSSH: {},
  20. }
  21. const MountSharingShared = "shared"
  22. const MountSharingPrivate = "private"
  23. const MountSharingLocked = "locked"
  24. var allowedSharingTypes = map[string]struct{}{
  25. MountSharingShared: {},
  26. MountSharingPrivate: {},
  27. MountSharingLocked: {},
  28. }
  29. type mountsKeyT string
  30. var mountsKey = mountsKeyT("dockerfile/run/mounts")
  31. func init() {
  32. parseRunPreHooks = append(parseRunPreHooks, runMountPreHook)
  33. parseRunPostHooks = append(parseRunPostHooks, runMountPostHook)
  34. }
  35. func isValidMountType(s string) bool {
  36. if s == "secret" {
  37. if !isSecretMountsSupported() {
  38. return false
  39. }
  40. }
  41. if s == "ssh" {
  42. if !isSSHMountsSupported() {
  43. return false
  44. }
  45. }
  46. _, ok := allowedMountTypes[s]
  47. return ok
  48. }
  49. func runMountPreHook(cmd *RunCommand, req parseRequest) error {
  50. st := &mountState{}
  51. st.flag = req.flags.AddStrings("mount")
  52. cmd.setExternalValue(mountsKey, st)
  53. return nil
  54. }
  55. func runMountPostHook(cmd *RunCommand, req parseRequest) error {
  56. st := getMountState(cmd)
  57. if st == nil {
  58. return errors.Errorf("no mount state")
  59. }
  60. var mounts []*Mount
  61. for _, str := range st.flag.StringValues {
  62. m, err := parseMount(str)
  63. if err != nil {
  64. return err
  65. }
  66. mounts = append(mounts, m)
  67. }
  68. st.mounts = mounts
  69. return nil
  70. }
  71. func getMountState(cmd *RunCommand) *mountState {
  72. v := cmd.getExternalValue(mountsKey)
  73. if v == nil {
  74. return nil
  75. }
  76. return v.(*mountState)
  77. }
  78. func GetMounts(cmd *RunCommand) []*Mount {
  79. return getMountState(cmd).mounts
  80. }
  81. type mountState struct {
  82. flag *Flag
  83. mounts []*Mount
  84. }
  85. type Mount struct {
  86. Type string
  87. From string
  88. Source string
  89. Target string
  90. ReadOnly bool
  91. CacheID string
  92. CacheSharing string
  93. Required bool
  94. Mode *uint64
  95. UID *uint64
  96. GID *uint64
  97. }
  98. func parseMount(value string) (*Mount, error) {
  99. csvReader := csv.NewReader(strings.NewReader(value))
  100. fields, err := csvReader.Read()
  101. if err != nil {
  102. return nil, errors.Wrap(err, "failed to parse csv mounts")
  103. }
  104. m := &Mount{Type: MountTypeBind}
  105. roAuto := true
  106. for _, field := range fields {
  107. parts := strings.SplitN(field, "=", 2)
  108. key := strings.ToLower(parts[0])
  109. if len(parts) == 1 {
  110. switch key {
  111. case "readonly", "ro":
  112. m.ReadOnly = true
  113. roAuto = false
  114. continue
  115. case "readwrite", "rw":
  116. m.ReadOnly = false
  117. roAuto = false
  118. continue
  119. case "required":
  120. if m.Type == "secret" || m.Type == "ssh" {
  121. m.Required = true
  122. continue
  123. } else {
  124. return nil, errors.Errorf("unexpected key '%s' for mount type '%s'", key, m.Type)
  125. }
  126. }
  127. }
  128. if len(parts) != 2 {
  129. return nil, errors.Errorf("invalid field '%s' must be a key=value pair", field)
  130. }
  131. value := parts[1]
  132. switch key {
  133. case "type":
  134. if !isValidMountType(strings.ToLower(value)) {
  135. return nil, errors.Errorf("unsupported mount type %q", value)
  136. }
  137. m.Type = strings.ToLower(value)
  138. case "from":
  139. m.From = value
  140. case "source", "src":
  141. m.Source = value
  142. case "target", "dst", "destination":
  143. m.Target = value
  144. case "readonly", "ro":
  145. m.ReadOnly, err = strconv.ParseBool(value)
  146. if err != nil {
  147. return nil, errors.Errorf("invalid value for %s: %s", key, value)
  148. }
  149. roAuto = false
  150. case "readwrite", "rw":
  151. rw, err := strconv.ParseBool(value)
  152. if err != nil {
  153. return nil, errors.Errorf("invalid value for %s: %s", key, value)
  154. }
  155. m.ReadOnly = !rw
  156. roAuto = false
  157. case "required":
  158. if m.Type == "secret" || m.Type == "ssh" {
  159. v, err := strconv.ParseBool(value)
  160. if err != nil {
  161. return nil, errors.Errorf("invalid value for %s: %s", key, value)
  162. }
  163. m.Required = v
  164. } else {
  165. return nil, errors.Errorf("unexpected key '%s' for mount type '%s'", key, m.Type)
  166. }
  167. case "id":
  168. m.CacheID = value
  169. case "sharing":
  170. if _, ok := allowedSharingTypes[strings.ToLower(value)]; !ok {
  171. return nil, errors.Errorf("unsupported sharing value %q", value)
  172. }
  173. m.CacheSharing = strings.ToLower(value)
  174. case "mode":
  175. mode, err := strconv.ParseUint(value, 8, 32)
  176. if err != nil {
  177. return nil, errors.Errorf("invalid value %s for mode", value)
  178. }
  179. m.Mode = &mode
  180. case "uid":
  181. uid, err := strconv.ParseUint(value, 10, 32)
  182. if err != nil {
  183. return nil, errors.Errorf("invalid value %s for uid", value)
  184. }
  185. m.UID = &uid
  186. case "gid":
  187. gid, err := strconv.ParseUint(value, 10, 32)
  188. if err != nil {
  189. return nil, errors.Errorf("invalid value %s for gid", value)
  190. }
  191. m.GID = &gid
  192. default:
  193. return nil, errors.Errorf("unexpected key '%s' in '%s'", key, field)
  194. }
  195. }
  196. fileInfoAllowed := m.Type == MountTypeSecret || m.Type == MountTypeSSH || m.Type == MountTypeCache
  197. if m.Mode != nil && !fileInfoAllowed {
  198. return nil, errors.Errorf("mode not allowed for %q type mounts", m.Type)
  199. }
  200. if m.UID != nil && !fileInfoAllowed {
  201. return nil, errors.Errorf("uid not allowed for %q type mounts", m.Type)
  202. }
  203. if m.GID != nil && !fileInfoAllowed {
  204. return nil, errors.Errorf("gid not allowed for %q type mounts", m.Type)
  205. }
  206. if roAuto {
  207. if m.Type == MountTypeCache || m.Type == MountTypeTmpfs {
  208. m.ReadOnly = false
  209. } else {
  210. m.ReadOnly = true
  211. }
  212. }
  213. if m.CacheSharing != "" && m.Type != MountTypeCache {
  214. return nil, errors.Errorf("invalid cache sharing set for %v mount", m.Type)
  215. }
  216. if m.Type == MountTypeSecret {
  217. if m.From != "" {
  218. return nil, errors.Errorf("secret mount should not have a from")
  219. }
  220. if m.CacheSharing != "" {
  221. return nil, errors.Errorf("secret mount should not define sharing")
  222. }
  223. if m.Source == "" && m.Target == "" && m.CacheID == "" {
  224. return nil, errors.Errorf("invalid secret mount. one of source, target required")
  225. }
  226. if m.Source != "" && m.CacheID != "" {
  227. return nil, errors.Errorf("both source and id can't be set")
  228. }
  229. }
  230. return m, nil
  231. }