github.com/quite/nomad@v0.8.6/client/driver/env/env.go (about) 1 package env 2 3 import ( 4 "fmt" 5 "net" 6 "os" 7 "strconv" 8 "strings" 9 "sync" 10 11 cstructs "github.com/hashicorp/nomad/client/structs" 12 "github.com/hashicorp/nomad/helper" 13 hargs "github.com/hashicorp/nomad/helper/args" 14 "github.com/hashicorp/nomad/nomad/structs" 15 ) 16 17 // A set of environment variables that are exported by each driver. 18 const ( 19 // AllocDir is the environment variable with the path to the alloc directory 20 // that is shared across tasks within a task group. 21 AllocDir = "NOMAD_ALLOC_DIR" 22 23 // TaskLocalDir is the environment variable with the path to the tasks local 24 // directory where it can store data that is persisted to the alloc is 25 // removed. 26 TaskLocalDir = "NOMAD_TASK_DIR" 27 28 // SecretsDir is the environment variable with the path to the tasks secret 29 // directory where it can store sensitive data. 30 SecretsDir = "NOMAD_SECRETS_DIR" 31 32 // MemLimit is the environment variable with the tasks memory limit in MBs. 33 MemLimit = "NOMAD_MEMORY_LIMIT" 34 35 // CpuLimit is the environment variable with the tasks CPU limit in MHz. 36 CpuLimit = "NOMAD_CPU_LIMIT" 37 38 // AllocID is the environment variable for passing the allocation ID. 39 AllocID = "NOMAD_ALLOC_ID" 40 41 // AllocName is the environment variable for passing the allocation name. 42 AllocName = "NOMAD_ALLOC_NAME" 43 44 // TaskName is the environment variable for passing the task name. 45 TaskName = "NOMAD_TASK_NAME" 46 47 // GroupName is the environment variable for passing the task group name. 48 GroupName = "NOMAD_GROUP_NAME" 49 50 // JobName is the environment variable for passing the job name. 51 JobName = "NOMAD_JOB_NAME" 52 53 // AllocIndex is the environment variable for passing the allocation index. 54 AllocIndex = "NOMAD_ALLOC_INDEX" 55 56 // Datacenter is the environment variable for passing the datacenter in which the alloc is running. 57 Datacenter = "NOMAD_DC" 58 59 // Region is the environment variable for passing the region in which the alloc is running. 60 Region = "NOMAD_REGION" 61 62 // AddrPrefix is the prefix for passing both dynamic and static port 63 // allocations to tasks. 64 // E.g $NOMAD_ADDR_http=127.0.0.1:80 65 // 66 // The ip:port are always the host's. 67 AddrPrefix = "NOMAD_ADDR_" 68 69 // IpPrefix is the prefix for passing the host IP of a port allocation 70 // to a task. 71 IpPrefix = "NOMAD_IP_" 72 73 // PortPrefix is the prefix for passing the port allocation to a task. 74 // It will be the task's port if a port map is specified. Task's should 75 // bind to this port. 76 PortPrefix = "NOMAD_PORT_" 77 78 // HostPortPrefix is the prefix for passing the host port when a port 79 // map is specified. 80 HostPortPrefix = "NOMAD_HOST_PORT_" 81 82 // MetaPrefix is the prefix for passing task meta data. 83 MetaPrefix = "NOMAD_META_" 84 85 // VaultToken is the environment variable for passing the Vault token 86 VaultToken = "VAULT_TOKEN" 87 ) 88 89 // The node values that can be interpreted. 90 const ( 91 nodeIdKey = "node.unique.id" 92 nodeDcKey = "node.datacenter" 93 nodeRegionKey = "node.region" 94 nodeNameKey = "node.unique.name" 95 nodeClassKey = "node.class" 96 97 // Prefixes used for lookups. 98 nodeAttributePrefix = "attr." 99 nodeMetaPrefix = "meta." 100 ) 101 102 // TaskEnv is a task's environment as well as node attribute's for 103 // interpolation. 104 type TaskEnv struct { 105 // NodeAttrs is the map of node attributes for interpolation 106 NodeAttrs map[string]string 107 108 // EnvMap is the map of environment variables 109 EnvMap map[string]string 110 111 // envList is a memoized list created by List() 112 envList []string 113 } 114 115 // NewTaskEnv creates a new task environment with the given environment and 116 // node attribute maps. 117 func NewTaskEnv(env, node map[string]string) *TaskEnv { 118 return &TaskEnv{ 119 NodeAttrs: node, 120 EnvMap: env, 121 } 122 } 123 124 // List returns the task's environment as a slice of NAME=value pair strings. 125 func (t *TaskEnv) List() []string { 126 if t.envList != nil { 127 return t.envList 128 } 129 130 env := []string{} 131 for k, v := range t.EnvMap { 132 env = append(env, fmt.Sprintf("%s=%s", k, v)) 133 } 134 135 return env 136 } 137 138 // Map of the task's environment variables. 139 func (t *TaskEnv) Map() map[string]string { 140 m := make(map[string]string, len(t.EnvMap)) 141 for k, v := range t.EnvMap { 142 m[k] = v 143 } 144 145 return m 146 } 147 148 // All of the task's environment variables and the node's attributes in a 149 // single map. 150 func (t *TaskEnv) All() map[string]string { 151 m := make(map[string]string, len(t.EnvMap)+len(t.NodeAttrs)) 152 for k, v := range t.EnvMap { 153 m[k] = v 154 } 155 for k, v := range t.NodeAttrs { 156 m[k] = v 157 } 158 159 return m 160 } 161 162 // ParseAndReplace takes the user supplied args replaces any instance of an 163 // environment variable or Nomad variable in the args with the actual value. 164 func (t *TaskEnv) ParseAndReplace(args []string) []string { 165 if args == nil { 166 return nil 167 } 168 169 replaced := make([]string, len(args)) 170 for i, arg := range args { 171 replaced[i] = hargs.ReplaceEnv(arg, t.EnvMap, t.NodeAttrs) 172 } 173 174 return replaced 175 } 176 177 // ReplaceEnv takes an arg and replaces all occurrences of environment variables 178 // and Nomad variables. If the variable is found in the passed map it is 179 // replaced, otherwise the original string is returned. 180 func (t *TaskEnv) ReplaceEnv(arg string) string { 181 return hargs.ReplaceEnv(arg, t.EnvMap, t.NodeAttrs) 182 } 183 184 // Builder is used to build task environment's and is safe for concurrent use. 185 type Builder struct { 186 // envvars are custom set environment variables 187 envvars map[string]string 188 189 // templateEnv are env vars set from templates 190 templateEnv map[string]string 191 192 // hostEnv are environment variables filtered from the host 193 hostEnv map[string]string 194 195 // nodeAttrs are Node attributes and metadata 196 nodeAttrs map[string]string 197 198 // taskMeta are the meta attributes on the task 199 taskMeta map[string]string 200 201 // allocDir from task's perspective; eg /alloc 202 allocDir string 203 204 // localDir from task's perspective; eg /local 205 localDir string 206 207 // secretsDir from task's perspective; eg /secrets 208 secretsDir string 209 210 cpuLimit int 211 memLimit int 212 taskName string 213 allocIndex int 214 datacenter string 215 region string 216 allocId string 217 allocName string 218 groupName string 219 vaultToken string 220 injectVaultToken bool 221 jobName string 222 223 // otherPorts for tasks in the same alloc 224 otherPorts map[string]string 225 226 // driverNetwork is the network defined by the driver (or nil if none 227 // was defined). 228 driverNetwork *cstructs.DriverNetwork 229 230 // network resources from the task; must be lazily turned into env vars 231 // because portMaps and advertiseIP can change after builder creation 232 // and affect network env vars. 233 networks []*structs.NetworkResource 234 235 mu *sync.RWMutex 236 } 237 238 // NewBuilder creates a new task environment builder. 239 func NewBuilder(node *structs.Node, alloc *structs.Allocation, task *structs.Task, region string) *Builder { 240 b := &Builder{ 241 region: region, 242 mu: &sync.RWMutex{}, 243 } 244 return b.setTask(task).setAlloc(alloc).setNode(node) 245 } 246 247 // NewEmptyBuilder creates a new environment builder. 248 func NewEmptyBuilder() *Builder { 249 return &Builder{ 250 mu: &sync.RWMutex{}, 251 } 252 } 253 254 // Build must be called after all the tasks environment values have been set. 255 func (b *Builder) Build() *TaskEnv { 256 nodeAttrs := make(map[string]string) 257 envMap := make(map[string]string) 258 259 b.mu.RLock() 260 defer b.mu.RUnlock() 261 262 // Add the directories 263 if b.allocDir != "" { 264 envMap[AllocDir] = b.allocDir 265 } 266 if b.localDir != "" { 267 envMap[TaskLocalDir] = b.localDir 268 } 269 if b.secretsDir != "" { 270 envMap[SecretsDir] = b.secretsDir 271 } 272 273 // Add the resource limits 274 if b.memLimit != 0 { 275 envMap[MemLimit] = strconv.Itoa(b.memLimit) 276 } 277 if b.cpuLimit != 0 { 278 envMap[CpuLimit] = strconv.Itoa(b.cpuLimit) 279 } 280 281 // Add the task metadata 282 if b.allocId != "" { 283 envMap[AllocID] = b.allocId 284 } 285 if b.allocName != "" { 286 envMap[AllocName] = b.allocName 287 } 288 if b.groupName != "" { 289 envMap[GroupName] = b.groupName 290 } 291 if b.allocIndex != -1 { 292 envMap[AllocIndex] = strconv.Itoa(b.allocIndex) 293 } 294 if b.taskName != "" { 295 envMap[TaskName] = b.taskName 296 } 297 if b.jobName != "" { 298 envMap[JobName] = b.jobName 299 } 300 if b.datacenter != "" { 301 envMap[Datacenter] = b.datacenter 302 } 303 if b.region != "" { 304 envMap[Region] = b.region 305 306 // Copy region over to node attrs 307 nodeAttrs[nodeRegionKey] = b.region 308 } 309 310 // Build the network related env vars 311 buildNetworkEnv(envMap, b.networks, b.driverNetwork) 312 313 // Build the addr of the other tasks 314 for k, v := range b.otherPorts { 315 envMap[k] = v 316 } 317 318 // Build the Vault Token 319 if b.injectVaultToken && b.vaultToken != "" { 320 envMap[VaultToken] = b.vaultToken 321 } 322 323 // Copy task meta 324 for k, v := range b.taskMeta { 325 envMap[k] = v 326 } 327 328 // Copy node attributes 329 for k, v := range b.nodeAttrs { 330 nodeAttrs[k] = v 331 } 332 333 // Interpolate and add environment variables 334 for k, v := range b.hostEnv { 335 envMap[k] = hargs.ReplaceEnv(v, nodeAttrs, envMap) 336 } 337 338 // Copy interpolated task env vars second as they override host env vars 339 for k, v := range b.envvars { 340 envMap[k] = hargs.ReplaceEnv(v, nodeAttrs, envMap) 341 } 342 343 // Copy template env vars third as they override task env vars 344 for k, v := range b.templateEnv { 345 envMap[k] = v 346 } 347 348 // Clean keys (see #2405) 349 cleanedEnv := make(map[string]string, len(envMap)) 350 for k, v := range envMap { 351 cleanedK := helper.CleanEnvVar(k, '_') 352 cleanedEnv[cleanedK] = v 353 } 354 355 return NewTaskEnv(cleanedEnv, nodeAttrs) 356 } 357 358 // Update task updates the environment based on a new alloc and task. 359 func (b *Builder) UpdateTask(alloc *structs.Allocation, task *structs.Task) *Builder { 360 b.mu.Lock() 361 defer b.mu.Unlock() 362 return b.setTask(task).setAlloc(alloc) 363 } 364 365 // setTask is called from NewBuilder to populate task related environment 366 // variables. 367 func (b *Builder) setTask(task *structs.Task) *Builder { 368 b.taskName = task.Name 369 b.envvars = make(map[string]string, len(task.Env)) 370 for k, v := range task.Env { 371 b.envvars[k] = v 372 } 373 if task.Resources == nil { 374 b.memLimit = 0 375 b.cpuLimit = 0 376 b.networks = []*structs.NetworkResource{} 377 } else { 378 b.memLimit = task.Resources.MemoryMB 379 b.cpuLimit = task.Resources.CPU 380 // Copy networks to prevent sharing 381 b.networks = make([]*structs.NetworkResource, len(task.Resources.Networks)) 382 for i, n := range task.Resources.Networks { 383 b.networks[i] = n.Copy() 384 } 385 } 386 return b 387 } 388 389 // setAlloc is called from NewBuilder to populate alloc related environment 390 // variables. 391 func (b *Builder) setAlloc(alloc *structs.Allocation) *Builder { 392 b.allocId = alloc.ID 393 b.allocName = alloc.Name 394 b.groupName = alloc.TaskGroup 395 b.allocIndex = int(alloc.Index()) 396 b.jobName = alloc.Job.Name 397 398 // Set meta 399 combined := alloc.Job.CombinedTaskMeta(alloc.TaskGroup, b.taskName) 400 // taskMetaSize is double to total meta keys to account for given and upper 401 // cased values 402 taskMetaSize := len(combined) * 2 403 404 // if job is parameterized initialize optional meta to empty strings 405 if alloc.Job.Dispatched { 406 optionalMetaCount := len(alloc.Job.ParameterizedJob.MetaOptional) 407 b.taskMeta = make(map[string]string, taskMetaSize+optionalMetaCount*2) 408 409 for _, k := range alloc.Job.ParameterizedJob.MetaOptional { 410 b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, strings.ToUpper(k))] = "" 411 b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, k)] = "" 412 } 413 } else { 414 b.taskMeta = make(map[string]string, taskMetaSize) 415 } 416 417 for k, v := range combined { 418 b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, strings.ToUpper(k))] = v 419 b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, k)] = v 420 } 421 422 // Add ports from other tasks 423 b.otherPorts = make(map[string]string, len(alloc.TaskResources)*2) 424 for taskName, resources := range alloc.TaskResources { 425 if taskName == b.taskName { 426 continue 427 } 428 for _, nw := range resources.Networks { 429 for _, p := range nw.ReservedPorts { 430 addPort(b.otherPorts, taskName, nw.IP, p.Label, p.Value) 431 } 432 for _, p := range nw.DynamicPorts { 433 addPort(b.otherPorts, taskName, nw.IP, p.Label, p.Value) 434 } 435 } 436 } 437 return b 438 } 439 440 // setNode is called from NewBuilder to populate node attributes. 441 func (b *Builder) setNode(n *structs.Node) *Builder { 442 b.nodeAttrs = make(map[string]string, 4+len(n.Attributes)+len(n.Meta)) 443 b.nodeAttrs[nodeIdKey] = n.ID 444 b.nodeAttrs[nodeNameKey] = n.Name 445 b.nodeAttrs[nodeClassKey] = n.NodeClass 446 b.nodeAttrs[nodeDcKey] = n.Datacenter 447 b.datacenter = n.Datacenter 448 449 // Set up the attributes. 450 for k, v := range n.Attributes { 451 b.nodeAttrs[fmt.Sprintf("%s%s", nodeAttributePrefix, k)] = v 452 } 453 454 // Set up the meta. 455 for k, v := range n.Meta { 456 b.nodeAttrs[fmt.Sprintf("%s%s", nodeMetaPrefix, k)] = v 457 } 458 return b 459 } 460 461 func (b *Builder) SetAllocDir(dir string) *Builder { 462 b.mu.Lock() 463 b.allocDir = dir 464 b.mu.Unlock() 465 return b 466 } 467 468 func (b *Builder) SetTaskLocalDir(dir string) *Builder { 469 b.mu.Lock() 470 b.localDir = dir 471 b.mu.Unlock() 472 return b 473 } 474 475 func (b *Builder) SetSecretsDir(dir string) *Builder { 476 b.mu.Lock() 477 b.secretsDir = dir 478 b.mu.Unlock() 479 return b 480 } 481 482 // SetDriverNetwork defined by the driver. 483 func (b *Builder) SetDriverNetwork(n *cstructs.DriverNetwork) *Builder { 484 ncopy := n.Copy() 485 b.mu.Lock() 486 b.driverNetwork = ncopy 487 b.mu.Unlock() 488 return b 489 } 490 491 // buildNetworkEnv env vars in the given map. 492 // 493 // Auto: NOMAD_PORT_<label> 494 // Host: NOMAD_IP_<label>, NOMAD_ADDR_<label>, NOMAD_HOST_PORT_<label> 495 // 496 // Handled by setAlloc -> otherPorts: 497 // 498 // Task: NOMAD_TASK_{IP,PORT,ADDR}_<task>_<label> # Always host values 499 // 500 func buildNetworkEnv(envMap map[string]string, nets structs.Networks, driverNet *cstructs.DriverNetwork) { 501 for _, n := range nets { 502 for _, p := range n.ReservedPorts { 503 buildPortEnv(envMap, p, n.IP, driverNet) 504 } 505 for _, p := range n.DynamicPorts { 506 buildPortEnv(envMap, p, n.IP, driverNet) 507 } 508 } 509 } 510 511 func buildPortEnv(envMap map[string]string, p structs.Port, ip string, driverNet *cstructs.DriverNetwork) { 512 // Host IP, port, and address 513 portStr := strconv.Itoa(p.Value) 514 envMap[IpPrefix+p.Label] = ip 515 envMap[HostPortPrefix+p.Label] = portStr 516 envMap[AddrPrefix+p.Label] = net.JoinHostPort(ip, portStr) 517 518 // Set Port to task's value if there's a port map 519 if driverNet != nil && driverNet.PortMap[p.Label] != 0 { 520 envMap[PortPrefix+p.Label] = strconv.Itoa(driverNet.PortMap[p.Label]) 521 } else { 522 // Default to host's 523 envMap[PortPrefix+p.Label] = portStr 524 } 525 } 526 527 // SetHostEnvvars adds the host environment variables to the tasks. The filter 528 // parameter can be use to filter host environment from entering the tasks. 529 func (b *Builder) SetHostEnvvars(filter []string) *Builder { 530 filterMap := make(map[string]struct{}, len(filter)) 531 for _, f := range filter { 532 filterMap[f] = struct{}{} 533 } 534 535 fullHostEnv := os.Environ() 536 filteredHostEnv := make(map[string]string, len(fullHostEnv)) 537 for _, e := range fullHostEnv { 538 parts := strings.SplitN(e, "=", 2) 539 key, value := parts[0], parts[1] 540 541 // Skip filtered environment variables 542 if _, filtered := filterMap[key]; filtered { 543 continue 544 } 545 546 filteredHostEnv[key] = value 547 } 548 549 b.mu.Lock() 550 b.hostEnv = filteredHostEnv 551 b.mu.Unlock() 552 return b 553 } 554 555 func (b *Builder) SetTemplateEnv(m map[string]string) *Builder { 556 b.mu.Lock() 557 b.templateEnv = m 558 b.mu.Unlock() 559 return b 560 } 561 562 func (b *Builder) SetVaultToken(token string, inject bool) *Builder { 563 b.mu.Lock() 564 b.vaultToken = token 565 b.injectVaultToken = inject 566 b.mu.Unlock() 567 return b 568 } 569 570 // addPort keys and values for other tasks to an env var map 571 func addPort(m map[string]string, taskName, ip, portLabel string, port int) { 572 key := fmt.Sprintf("%s%s_%s", AddrPrefix, taskName, portLabel) 573 m[key] = fmt.Sprintf("%s:%d", ip, port) 574 key = fmt.Sprintf("%s%s_%s", IpPrefix, taskName, portLabel) 575 m[key] = ip 576 key = fmt.Sprintf("%s%s_%s", PortPrefix, taskName, portLabel) 577 m[key] = strconv.Itoa(port) 578 }