package env import ( "fmt" "os" "strconv" "strings" 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" // 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" // AllocIndex is the environment variable for passing the allocation index. AllocIndex = "NOMAD_ALLOC_INDEX" // 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_" ) // 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 TaskGroupMeta map[string]string JobMeta map[string]string AllocDir string TaskDir string CpuLimit int MemLimit int TaskName string AllocIndex int AllocId string AllocName string Node *structs.Node Networks []*structs.NetworkResource PortMap map[string]int // 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 with the following precedence: task, task group, job. for _, meta := range []map[string]string{t.JobMeta, t.TaskGroupMeta, t.TaskMeta} { for k, v := range meta { t.TaskEnv[fmt.Sprintf("%s%s", MetaPrefix, strings.ToUpper(k))] = v } } // Build the ports for _, network := range t.Networks { for label, value := range network.MapLabelToValues(t.PortMap) { IPPort := fmt.Sprintf("%s:%d", network.IP, value) t.TaskEnv[fmt.Sprintf("%s%s", AddrPrefix, label)] = IPPort t.TaskEnv[fmt.Sprintf("%s%s", IpPrefix, label)] = network.IP t.TaskEnv[fmt.Sprintf("%s%s", PortPrefix, label)] = fmt.Sprintf("%d", value) // Pass an explicit port mapping to the environment if port, ok := t.PortMap[label]; ok { t.TaskEnv[fmt.Sprintf("%s%s", HostPortPrefix, label)] = strconv.Itoa(port) } } } // Build the directories if t.AllocDir != "" { t.TaskEnv[AllocDir] = t.AllocDir } if t.TaskDir != "" { t.TaskEnv[TaskLocalDir] = t.TaskDir } // 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 } // 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 } } // 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 } 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 } // 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) 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) SetTaskGroupMeta(m map[string]string) *TaskEnvironment { t.TaskGroupMeta = m return t } func (t *TaskEnvironment) ClearTaskGroupMeta() *TaskEnvironment { t.TaskGroupMeta = nil return t } func (t *TaskEnvironment) SetJobMeta(m map[string]string) *TaskEnvironment { t.JobMeta = m return t } func (t *TaskEnvironment) ClearJobMeta() *TaskEnvironment { t.JobMeta = 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() 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 }