context.go 4.9 KB

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