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