context.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package template
  2. import (
  3. "bytes"
  4. "fmt"
  5. "strings"
  6. "text/template"
  7. "github.com/docker/swarmkit/agent/configs"
  8. "github.com/docker/swarmkit/agent/exec"
  9. "github.com/docker/swarmkit/agent/secrets"
  10. "github.com/docker/swarmkit/api"
  11. "github.com/docker/swarmkit/api/naming"
  12. "github.com/pkg/errors"
  13. )
  14. // Platform holds information about the underlying platform of the node
  15. type Platform struct {
  16. Architecture string
  17. OS string
  18. }
  19. // Context defines the strict set of values that can be injected into a
  20. // template expression in SwarmKit data structure.
  21. // NOTE: Be very careful adding any fields to this structure with types
  22. // that have methods defined on them. The template would be able to
  23. // invoke those methods.
  24. type Context struct {
  25. Service struct {
  26. ID string
  27. Name string
  28. Labels map[string]string
  29. }
  30. Node struct {
  31. ID string
  32. Hostname string
  33. Platform Platform
  34. }
  35. Task struct {
  36. ID string
  37. Name string
  38. Slot string
  39. // NOTE(stevvooe): Why no labels here? Tasks don't actually have labels
  40. // (from a user perspective). The labels are part of the container! If
  41. // one wants to use labels for templating, use service labels!
  42. }
  43. }
  44. // NewContext returns a new template context from the data available in the
  45. // task and the node where it is scheduled to run.
  46. // The provided context can then be used to populate runtime values in a
  47. // ContainerSpec.
  48. func NewContext(n *api.NodeDescription, t *api.Task) (ctx Context) {
  49. ctx.Service.ID = t.ServiceID
  50. ctx.Service.Name = t.ServiceAnnotations.Name
  51. ctx.Service.Labels = t.ServiceAnnotations.Labels
  52. ctx.Node.ID = t.NodeID
  53. // Add node information to context only if we have them available
  54. if n != nil {
  55. ctx.Node.Hostname = n.Hostname
  56. ctx.Node.Platform = Platform{
  57. Architecture: n.Platform.Architecture,
  58. OS: n.Platform.OS,
  59. }
  60. }
  61. ctx.Task.ID = t.ID
  62. ctx.Task.Name = naming.Task(t)
  63. if t.Slot != 0 {
  64. ctx.Task.Slot = fmt.Sprint(t.Slot)
  65. } else {
  66. // fall back to node id for slot when there is no slot
  67. ctx.Task.Slot = t.NodeID
  68. }
  69. return
  70. }
  71. // Expand treats the string s as a template and populates it with values from
  72. // the context.
  73. func (ctx *Context) Expand(s string) (string, error) {
  74. tmpl, err := newTemplate(s, nil)
  75. if err != nil {
  76. return s, err
  77. }
  78. var buf bytes.Buffer
  79. if err := tmpl.Execute(&buf, ctx); err != nil {
  80. return s, err
  81. }
  82. return buf.String(), nil
  83. }
  84. // PayloadContext provides a context for expanding a config or secret payload.
  85. // NOTE: Be very careful adding any fields to this structure with types
  86. // that have methods defined on them. The template would be able to
  87. // invoke those methods.
  88. type PayloadContext struct {
  89. Context
  90. t *api.Task
  91. restrictedSecrets exec.SecretGetter
  92. restrictedConfigs exec.ConfigGetter
  93. sensitive bool
  94. }
  95. func (ctx *PayloadContext) secretGetter(target string) (string, error) {
  96. if ctx.restrictedSecrets == nil {
  97. return "", errors.New("secrets unavailable")
  98. }
  99. container := ctx.t.Spec.GetContainer()
  100. if container == nil {
  101. return "", errors.New("task is not a container")
  102. }
  103. for _, secretRef := range container.Secrets {
  104. file := secretRef.GetFile()
  105. if file != nil && file.Name == target {
  106. secret, err := ctx.restrictedSecrets.Get(secretRef.SecretID)
  107. if err != nil {
  108. return "", err
  109. }
  110. ctx.sensitive = true
  111. return string(secret.Spec.Data), nil
  112. }
  113. }
  114. return "", errors.Errorf("secret target %s not found", target)
  115. }
  116. func (ctx *PayloadContext) configGetter(target string) (string, error) {
  117. if ctx.restrictedConfigs == nil {
  118. return "", errors.New("configs unavailable")
  119. }
  120. container := ctx.t.Spec.GetContainer()
  121. if container == nil {
  122. return "", errors.New("task is not a container")
  123. }
  124. for _, configRef := range container.Configs {
  125. file := configRef.GetFile()
  126. if file != nil && file.Name == target {
  127. config, err := ctx.restrictedConfigs.Get(configRef.ConfigID)
  128. if err != nil {
  129. return "", err
  130. }
  131. return string(config.Spec.Data), nil
  132. }
  133. }
  134. return "", errors.Errorf("config target %s not found", target)
  135. }
  136. func (ctx *PayloadContext) envGetter(variable string) (string, error) {
  137. container := ctx.t.Spec.GetContainer()
  138. if container == nil {
  139. return "", errors.New("task is not a container")
  140. }
  141. for _, env := range container.Env {
  142. parts := strings.SplitN(env, "=", 2)
  143. if len(parts) > 1 && parts[0] == variable {
  144. return parts[1], nil
  145. }
  146. }
  147. return "", nil
  148. }
  149. // NewPayloadContextFromTask returns a new template context from the data
  150. // available in the task and the node where it is scheduled to run.
  151. // This context also provides access to the configs
  152. // and secrets that the task has access to. The provided context can then
  153. // be used to populate runtime values in a templated config or secret.
  154. func NewPayloadContextFromTask(node *api.NodeDescription, t *api.Task, dependencies exec.DependencyGetter) (ctx PayloadContext) {
  155. return PayloadContext{
  156. Context: NewContext(node, t),
  157. t: t,
  158. restrictedSecrets: secrets.Restrict(dependencies.Secrets(), t),
  159. restrictedConfigs: configs.Restrict(dependencies.Configs(), t),
  160. }
  161. }
  162. // Expand treats the string s as a template and populates it with values from
  163. // the context.
  164. func (ctx *PayloadContext) Expand(s string) (string, error) {
  165. funcMap := template.FuncMap{
  166. "secret": ctx.secretGetter,
  167. "config": ctx.configGetter,
  168. "env": ctx.envGetter,
  169. }
  170. tmpl, err := newTemplate(s, funcMap)
  171. if err != nil {
  172. return s, err
  173. }
  174. var buf bytes.Buffer
  175. if err := tmpl.Execute(&buf, ctx); err != nil {
  176. return s, err
  177. }
  178. return buf.String(), nil
  179. }