package env import ( "fmt" "net" "os" "strconv" "strings" "github.com/hashicorp/nomad/helper" hargs "github.com/hashicorp/nomad/helper/args" "github.com/hashicorp/nomad/nomad/structs" ) // A set of environment variables that are exported by each driver. const ( // AllocDir is the environment variable with the path to the alloc directory // that is shared across tasks within a task group. AllocDir = "NOMAD_ALLOC_DIR" // TaskLocalDir is the environment variable with the path to the tasks local // directory where it can store data that is persisted to the alloc is // removed. TaskLocalDir = "NOMAD_TASK_DIR" // SecretsDir is the environment variable with the path to the tasks secret // directory where it can store sensitive data. SecretsDir = "NOMAD_SECRETS_DIR" // MemLimit is the environment variable with the tasks memory limit in MBs. MemLimit = "NOMAD_MEMORY_LIMIT" // CpuLimit is the environment variable with the tasks CPU limit in MHz. CpuLimit = "NOMAD_CPU_LIMIT" // AllocID is the environment variable for passing the allocation ID. AllocID = "NOMAD_ALLOC_ID" // AllocName is the environment variable for passing the allocation name. AllocName = "NOMAD_ALLOC_NAME" // TaskName is the environment variable for passing the task name. TaskName = "NOMAD_TASK_NAME" // JobName is the environment variable for passing the job name. JobName = "NOMAD_JOB_NAME" // AllocIndex is the environment variable for passing the allocation index. AllocIndex = "NOMAD_ALLOC_INDEX" // Datacenter is the environment variable for passing the datacenter in which the alloc is running. Datacenter = "NOMAD_DC" // Region is the environment variable for passing the region in which the alloc is running. Region = "NOMAD_REGION" // AddrPrefix is the prefix for passing both dynamic and static port // allocations to tasks. // E.g $NOMAD_ADDR_http=127.0.0.1:80 AddrPrefix = "NOMAD_ADDR_" // IpPrefix is the prefix for passing the IP of a port allocation to a task. IpPrefix = "NOMAD_IP_" // PortPrefix is the prefix for passing the port allocation to a task. PortPrefix = "NOMAD_PORT_" // HostPortPrefix is the prefix for passing the host port when a portmap is // specified. HostPortPrefix = "NOMAD_HOST_PORT_" // MetaPrefix is the prefix for passing task meta data. MetaPrefix = "NOMAD_META_" // VaultToken is the environment variable for passing the Vault token VaultToken = "VAULT_TOKEN" ) // The node values that can be interpreted. const ( nodeIdKey = "node.unique.id" nodeDcKey = "node.datacenter" nodeNameKey = "node.unique.name" nodeClassKey = "node.class" // Prefixes used for lookups. nodeAttributePrefix = "attr." nodeMetaPrefix = "meta." ) // TaskEnvironment is used to expose information to a task via environment // variables and provide interpolation of Nomad variables. type TaskEnvironment struct { Env map[string]string TaskMeta map[string]string AllocDir string TaskDir string SecretsDir string CpuLimit int MemLimit int TaskName string AllocIndex int Datacenter string Region string AllocId string AllocName string Node *structs.Node Networks []*structs.NetworkResource PortMap map[string]int VaultToken string InjectVaultToken bool JobName string Alloc *structs.Allocation // taskEnv is the variables that will be set in the tasks environment TaskEnv map[string]string // nodeValues is the values that are allowed for interprolation from the // node. NodeValues map[string]string } func NewTaskEnvironment(node *structs.Node) *TaskEnvironment { return &TaskEnvironment{Node: node, AllocIndex: -1} } // ParseAndReplace takes the user supplied args replaces any instance of an // environment variable or nomad variable in the args with the actual value. func (t *TaskEnvironment) ParseAndReplace(args []string) []string { replaced := make([]string, len(args)) for i, arg := range args { replaced[i] = hargs.ReplaceEnv(arg, t.TaskEnv, t.NodeValues) } return replaced } // ReplaceEnv takes an arg and replaces all occurrences of environment variables // and nomad variables. If the variable is found in the passed map it is // replaced, otherwise the original string is returned. func (t *TaskEnvironment) ReplaceEnv(arg string) string { return hargs.ReplaceEnv(arg, t.TaskEnv, t.NodeValues) } // Build must be called after all the tasks environment values have been set. func (t *TaskEnvironment) Build() *TaskEnvironment { t.NodeValues = make(map[string]string) t.TaskEnv = make(map[string]string) // Build the meta for k, v := range t.TaskMeta { t.TaskEnv[fmt.Sprintf("%s%s", MetaPrefix, strings.ToUpper(k))] = v t.TaskEnv[fmt.Sprintf("%s%s", MetaPrefix, k)] = v } // Build the ports for _, network := range t.Networks { for label, value := range network.MapLabelToValues(nil) { t.TaskEnv[fmt.Sprintf("%s%s", IpPrefix, label)] = network.IP t.TaskEnv[fmt.Sprintf("%s%s", HostPortPrefix, label)] = strconv.Itoa(value) if forwardedPort, ok := t.PortMap[label]; ok { value = forwardedPort } t.TaskEnv[fmt.Sprintf("%s%s", PortPrefix, label)] = strconv.Itoa(value) IPPort := net.JoinHostPort(network.IP, strconv.Itoa(value)) t.TaskEnv[fmt.Sprintf("%s%s", AddrPrefix, label)] = IPPort } } // Build the directories if t.AllocDir != "" { t.TaskEnv[AllocDir] = t.AllocDir } if t.TaskDir != "" { t.TaskEnv[TaskLocalDir] = t.TaskDir } if t.SecretsDir != "" { t.TaskEnv[SecretsDir] = t.SecretsDir } // Build the resource limits if t.MemLimit != 0 { t.TaskEnv[MemLimit] = strconv.Itoa(t.MemLimit) } if t.CpuLimit != 0 { t.TaskEnv[CpuLimit] = strconv.Itoa(t.CpuLimit) } // Build the tasks ids if t.AllocId != "" { t.TaskEnv[AllocID] = t.AllocId } if t.AllocName != "" { t.TaskEnv[AllocName] = t.AllocName } if t.AllocIndex != -1 { t.TaskEnv[AllocIndex] = strconv.Itoa(t.AllocIndex) } if t.TaskName != "" { t.TaskEnv[TaskName] = t.TaskName } if t.JobName != "" { t.TaskEnv[JobName] = t.JobName } if t.Datacenter != "" { t.TaskEnv[Datacenter] = t.Datacenter } if t.Region != "" { t.TaskEnv[Region] = t.Region } // Build the addr of the other tasks if t.Alloc != nil { for taskName, resources := range t.Alloc.TaskResources { if taskName == t.TaskName { continue } for _, nw := range resources.Networks { ports := make([]structs.Port, 0, len(nw.ReservedPorts)+len(nw.DynamicPorts)) for _, port := range nw.ReservedPorts { ports = append(ports, port) } for _, port := range nw.DynamicPorts { ports = append(ports, port) } for _, p := range ports { key := fmt.Sprintf("%s%s_%s", AddrPrefix, taskName, p.Label) t.TaskEnv[key] = fmt.Sprintf("%s:%d", nw.IP, p.Value) key = fmt.Sprintf("%s%s_%s", IpPrefix, taskName, p.Label) t.TaskEnv[key] = nw.IP key = fmt.Sprintf("%s%s_%s", PortPrefix, taskName, p.Label) t.TaskEnv[key] = strconv.Itoa(p.Value) } } } } // Build the node if t.Node != nil { // Set up the node values. t.NodeValues[nodeIdKey] = t.Node.ID t.NodeValues[nodeDcKey] = t.Node.Datacenter t.NodeValues[nodeNameKey] = t.Node.Name t.NodeValues[nodeClassKey] = t.Node.NodeClass // Set up the attributes. for k, v := range t.Node.Attributes { t.NodeValues[fmt.Sprintf("%s%s", nodeAttributePrefix, k)] = v } // Set up the meta. for k, v := range t.Node.Meta { t.NodeValues[fmt.Sprintf("%s%s", nodeMetaPrefix, k)] = v } } // Build the Vault Token if t.InjectVaultToken && t.VaultToken != "" { t.TaskEnv[VaultToken] = t.VaultToken } // Interpret the environment variables interpreted := make(map[string]string, len(t.Env)) for k, v := range t.Env { interpreted[k] = hargs.ReplaceEnv(v, t.NodeValues, t.TaskEnv) } for k, v := range interpreted { t.TaskEnv[k] = v } // Clean keys (see #2405) cleanedEnv := make(map[string]string, len(t.TaskEnv)) for k, v := range t.TaskEnv { cleanedK := helper.CleanEnvVar(k, '_') cleanedEnv[cleanedK] = v } t.TaskEnv = cleanedEnv return t } // EnvList returns a list of strings with NAME=value pairs. func (t *TaskEnvironment) EnvList() []string { env := []string{} for k, v := range t.TaskEnv { env = append(env, fmt.Sprintf("%s=%s", k, v)) } return env } // EnvMap returns a copy of the tasks environment variables. func (t *TaskEnvironment) EnvMap() map[string]string { m := make(map[string]string, len(t.TaskEnv)) for k, v := range t.TaskEnv { m[k] = v } return m } // EnvMapAll returns the environment variables that will be set as well as node // meta/attrs in the map. This is appropriate for interpolation. func (t *TaskEnvironment) EnvMapAll() map[string]string { m := make(map[string]string, len(t.TaskEnv)) for k, v := range t.TaskEnv { m[k] = v } for k, v := range t.NodeValues { m[k] = v } return m } // Builder methods to build the TaskEnvironment func (t *TaskEnvironment) SetAllocDir(dir string) *TaskEnvironment { t.AllocDir = dir return t } func (t *TaskEnvironment) ClearAllocDir() *TaskEnvironment { t.AllocDir = "" return t } func (t *TaskEnvironment) SetTaskLocalDir(dir string) *TaskEnvironment { t.TaskDir = dir return t } func (t *TaskEnvironment) ClearTaskLocalDir() *TaskEnvironment { t.TaskDir = "" return t } func (t *TaskEnvironment) SetSecretsDir(dir string) *TaskEnvironment { t.SecretsDir = dir return t } func (t *TaskEnvironment) ClearSecretsDir() *TaskEnvironment { t.SecretsDir = "" return t } func (t *TaskEnvironment) SetMemLimit(limit int) *TaskEnvironment { t.MemLimit = limit return t } func (t *TaskEnvironment) ClearMemLimit() *TaskEnvironment { t.MemLimit = 0 return t } func (t *TaskEnvironment) SetCpuLimit(limit int) *TaskEnvironment { t.CpuLimit = limit return t } func (t *TaskEnvironment) ClearCpuLimit() *TaskEnvironment { t.CpuLimit = 0 return t } func (t *TaskEnvironment) SetNetworks(networks []*structs.NetworkResource) *TaskEnvironment { t.Networks = networks return t } func (t *TaskEnvironment) clearNetworks() *TaskEnvironment { t.Networks = nil return t } func (t *TaskEnvironment) SetPortMap(portMap map[string]int) *TaskEnvironment { t.PortMap = portMap return t } func (t *TaskEnvironment) clearPortMap() *TaskEnvironment { t.PortMap = nil return t } // Takes a map of meta values to be passed to the task. The keys are capatilized // when the environent variable is set. func (t *TaskEnvironment) SetTaskMeta(m map[string]string) *TaskEnvironment { t.TaskMeta = m return t } func (t *TaskEnvironment) ClearTaskMeta() *TaskEnvironment { t.TaskMeta = nil return t } func (t *TaskEnvironment) SetEnvvars(m map[string]string) *TaskEnvironment { t.Env = m return t } // Appends the given environment variables. func (t *TaskEnvironment) AppendEnvvars(m map[string]string) *TaskEnvironment { if t.Env == nil { t.Env = make(map[string]string, len(m)) } for k, v := range m { t.Env[k] = v } return t } // AppendHostEnvvars adds the host environment variables to the tasks. The // filter parameter can be use to filter host environment from entering the // tasks. func (t *TaskEnvironment) AppendHostEnvvars(filter []string) *TaskEnvironment { hostEnv := os.Environ() if t.Env == nil { t.Env = make(map[string]string, len(hostEnv)) } // Index the filtered environment variables. index := make(map[string]struct{}, len(filter)) for _, f := range filter { index[f] = struct{}{} } for _, e := range hostEnv { parts := strings.SplitN(e, "=", 2) key, value := parts[0], parts[1] // Skip filtered environment variables if _, filtered := index[key]; filtered { continue } // Don't override the tasks environment variables. if _, existing := t.Env[key]; !existing { t.Env[key] = value } } return t } func (t *TaskEnvironment) ClearEnvvars() *TaskEnvironment { t.Env = nil return t } // Helper method for setting all fields from an allocation. func (t *TaskEnvironment) SetAlloc(alloc *structs.Allocation) *TaskEnvironment { t.AllocId = alloc.ID t.AllocName = alloc.Name t.AllocIndex = alloc.Index() t.Alloc = alloc return t } // Helper method for clearing all fields from an allocation. func (t *TaskEnvironment) ClearAlloc(alloc *structs.Allocation) *TaskEnvironment { return t.ClearAllocId().ClearAllocName().ClearAllocIndex() } func (t *TaskEnvironment) SetAllocIndex(index int) *TaskEnvironment { t.AllocIndex = index return t } func (t *TaskEnvironment) ClearAllocIndex() *TaskEnvironment { t.AllocIndex = -1 return t } func (t *TaskEnvironment) SetAllocId(id string) *TaskEnvironment { t.AllocId = id return t } func (t *TaskEnvironment) ClearAllocId() *TaskEnvironment { t.AllocId = "" return t } func (t *TaskEnvironment) SetAllocName(name string) *TaskEnvironment { t.AllocName = name return t } func (t *TaskEnvironment) ClearAllocName() *TaskEnvironment { t.AllocName = "" return t } func (t *TaskEnvironment) SetTaskName(name string) *TaskEnvironment { t.TaskName = name return t } func (t *TaskEnvironment) ClearTaskName() *TaskEnvironment { t.TaskName = "" return t } func (t *TaskEnvironment) SetJobName(name string) *TaskEnvironment { t.JobName = name return t } func (t *TaskEnvironment) ClearJobName() *TaskEnvironment { t.JobName = "" return t } func (t *TaskEnvironment) SetVaultToken(token string, inject bool) *TaskEnvironment { t.VaultToken = token t.InjectVaultToken = inject return t } func (t *TaskEnvironment) ClearVaultToken() *TaskEnvironment { t.VaultToken = "" t.InjectVaultToken = false return t }