github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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  	replaced := make([]string, len(args))
   166  	for i, arg := range args {
   167  		replaced[i] = hargs.ReplaceEnv(arg, t.EnvMap, t.NodeAttrs)
   168  	}
   169  
   170  	return replaced
   171  }
   172  
   173  // ReplaceEnv takes an arg and replaces all occurrences of environment variables
   174  // and Nomad variables.  If the variable is found in the passed map it is
   175  // replaced, otherwise the original string is returned.
   176  func (t *TaskEnv) ReplaceEnv(arg string) string {
   177  	return hargs.ReplaceEnv(arg, t.EnvMap, t.NodeAttrs)
   178  }
   179  
   180  // Builder is used to build task environment's and is safe for concurrent use.
   181  type Builder struct {
   182  	// envvars are custom set environment variables
   183  	envvars map[string]string
   184  
   185  	// templateEnv are env vars set from templates
   186  	templateEnv map[string]string
   187  
   188  	// hostEnv are environment variables filtered from the host
   189  	hostEnv map[string]string
   190  
   191  	// nodeAttrs are Node attributes and metadata
   192  	nodeAttrs map[string]string
   193  
   194  	// taskMeta are the meta attributes on the task
   195  	taskMeta map[string]string
   196  
   197  	// allocDir from task's perspective; eg /alloc
   198  	allocDir string
   199  
   200  	// localDir from task's perspective; eg /local
   201  	localDir string
   202  
   203  	// secrestsDir from task's perspective; eg /secrets
   204  	secretsDir string
   205  
   206  	cpuLimit         int
   207  	memLimit         int
   208  	taskName         string
   209  	allocIndex       int
   210  	datacenter       string
   211  	region           string
   212  	allocId          string
   213  	allocName        string
   214  	groupName        string
   215  	vaultToken       string
   216  	injectVaultToken bool
   217  	jobName          string
   218  
   219  	// otherPorts for tasks in the same alloc
   220  	otherPorts map[string]string
   221  
   222  	// driverNetwork is the network defined by the driver (or nil if none
   223  	// was defined).
   224  	driverNetwork *cstructs.DriverNetwork
   225  
   226  	// network resources from the task; must be lazily turned into env vars
   227  	// because portMaps and advertiseIP can change after builder creation
   228  	// and affect network env vars.
   229  	networks []*structs.NetworkResource
   230  
   231  	mu *sync.RWMutex
   232  }
   233  
   234  // NewBuilder creates a new task environment builder.
   235  func NewBuilder(node *structs.Node, alloc *structs.Allocation, task *structs.Task, region string) *Builder {
   236  	b := &Builder{
   237  		region: region,
   238  		mu:     &sync.RWMutex{},
   239  	}
   240  	return b.setTask(task).setAlloc(alloc).setNode(node)
   241  }
   242  
   243  // NewEmptyBuilder creates a new environment builder.
   244  func NewEmptyBuilder() *Builder {
   245  	return &Builder{
   246  		mu: &sync.RWMutex{},
   247  	}
   248  }
   249  
   250  // Build must be called after all the tasks environment values have been set.
   251  func (b *Builder) Build() *TaskEnv {
   252  	nodeAttrs := make(map[string]string)
   253  	envMap := make(map[string]string)
   254  
   255  	b.mu.RLock()
   256  	defer b.mu.RUnlock()
   257  
   258  	// Add the directories
   259  	if b.allocDir != "" {
   260  		envMap[AllocDir] = b.allocDir
   261  	}
   262  	if b.localDir != "" {
   263  		envMap[TaskLocalDir] = b.localDir
   264  	}
   265  	if b.secretsDir != "" {
   266  		envMap[SecretsDir] = b.secretsDir
   267  	}
   268  
   269  	// Add the resource limits
   270  	if b.memLimit != 0 {
   271  		envMap[MemLimit] = strconv.Itoa(b.memLimit)
   272  	}
   273  	if b.cpuLimit != 0 {
   274  		envMap[CpuLimit] = strconv.Itoa(b.cpuLimit)
   275  	}
   276  
   277  	// Add the task metadata
   278  	if b.allocId != "" {
   279  		envMap[AllocID] = b.allocId
   280  	}
   281  	if b.allocName != "" {
   282  		envMap[AllocName] = b.allocName
   283  	}
   284  	if b.groupName != "" {
   285  		envMap[GroupName] = b.groupName
   286  	}
   287  	if b.allocIndex != -1 {
   288  		envMap[AllocIndex] = strconv.Itoa(b.allocIndex)
   289  	}
   290  	if b.taskName != "" {
   291  		envMap[TaskName] = b.taskName
   292  	}
   293  	if b.jobName != "" {
   294  		envMap[JobName] = b.jobName
   295  	}
   296  	if b.datacenter != "" {
   297  		envMap[Datacenter] = b.datacenter
   298  	}
   299  	if b.region != "" {
   300  		envMap[Region] = b.region
   301  
   302  		// Copy region over to node attrs
   303  		nodeAttrs[nodeRegionKey] = b.region
   304  	}
   305  
   306  	// Build the network related env vars
   307  	buildNetworkEnv(envMap, b.networks, b.driverNetwork)
   308  
   309  	// Build the addr of the other tasks
   310  	for k, v := range b.otherPorts {
   311  		envMap[k] = v
   312  	}
   313  
   314  	// Build the Vault Token
   315  	if b.injectVaultToken && b.vaultToken != "" {
   316  		envMap[VaultToken] = b.vaultToken
   317  	}
   318  
   319  	// Copy task meta
   320  	for k, v := range b.taskMeta {
   321  		envMap[k] = v
   322  	}
   323  
   324  	// Copy node attributes
   325  	for k, v := range b.nodeAttrs {
   326  		nodeAttrs[k] = v
   327  	}
   328  
   329  	// Interpolate and add environment variables
   330  	for k, v := range b.hostEnv {
   331  		envMap[k] = hargs.ReplaceEnv(v, nodeAttrs, envMap)
   332  	}
   333  
   334  	// Copy interpolated task env vars second as they override host env vars
   335  	for k, v := range b.envvars {
   336  		envMap[k] = hargs.ReplaceEnv(v, nodeAttrs, envMap)
   337  	}
   338  
   339  	// Copy template env vars third as they override task env vars
   340  	for k, v := range b.templateEnv {
   341  		envMap[k] = v
   342  	}
   343  
   344  	// Clean keys (see #2405)
   345  	cleanedEnv := make(map[string]string, len(envMap))
   346  	for k, v := range envMap {
   347  		cleanedK := helper.CleanEnvVar(k, '_')
   348  		cleanedEnv[cleanedK] = v
   349  	}
   350  
   351  	return NewTaskEnv(cleanedEnv, nodeAttrs)
   352  }
   353  
   354  // Update task updates the environment based on a new alloc and task.
   355  func (b *Builder) UpdateTask(alloc *structs.Allocation, task *structs.Task) *Builder {
   356  	b.mu.Lock()
   357  	defer b.mu.Unlock()
   358  	return b.setTask(task).setAlloc(alloc)
   359  }
   360  
   361  // setTask is called from NewBuilder to populate task related environment
   362  // variables.
   363  func (b *Builder) setTask(task *structs.Task) *Builder {
   364  	b.taskName = task.Name
   365  	b.envvars = make(map[string]string, len(task.Env))
   366  	for k, v := range task.Env {
   367  		b.envvars[k] = v
   368  	}
   369  	if task.Resources == nil {
   370  		b.memLimit = 0
   371  		b.cpuLimit = 0
   372  		b.networks = []*structs.NetworkResource{}
   373  	} else {
   374  		b.memLimit = task.Resources.MemoryMB
   375  		b.cpuLimit = task.Resources.CPU
   376  		// Copy networks to prevent sharing
   377  		b.networks = make([]*structs.NetworkResource, len(task.Resources.Networks))
   378  		for i, n := range task.Resources.Networks {
   379  			b.networks[i] = n.Copy()
   380  		}
   381  	}
   382  	return b
   383  }
   384  
   385  // setAlloc is called from NewBuilder to populate alloc related environment
   386  // variables.
   387  func (b *Builder) setAlloc(alloc *structs.Allocation) *Builder {
   388  	b.allocId = alloc.ID
   389  	b.allocName = alloc.Name
   390  	b.groupName = alloc.TaskGroup
   391  	b.allocIndex = int(alloc.Index())
   392  	b.jobName = alloc.Job.Name
   393  
   394  	// Set meta
   395  	combined := alloc.Job.CombinedTaskMeta(alloc.TaskGroup, b.taskName)
   396  	b.taskMeta = make(map[string]string, len(combined)*2)
   397  	for k, v := range combined {
   398  		b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, strings.ToUpper(k))] = v
   399  		b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, k)] = v
   400  	}
   401  
   402  	// Add ports from other tasks
   403  	b.otherPorts = make(map[string]string, len(alloc.TaskResources)*2)
   404  	for taskName, resources := range alloc.TaskResources {
   405  		if taskName == b.taskName {
   406  			continue
   407  		}
   408  		for _, nw := range resources.Networks {
   409  			for _, p := range nw.ReservedPorts {
   410  				addPort(b.otherPorts, taskName, nw.IP, p.Label, p.Value)
   411  			}
   412  			for _, p := range nw.DynamicPorts {
   413  				addPort(b.otherPorts, taskName, nw.IP, p.Label, p.Value)
   414  			}
   415  		}
   416  	}
   417  	return b
   418  }
   419  
   420  // setNode is called from NewBuilder to populate node attributes.
   421  func (b *Builder) setNode(n *structs.Node) *Builder {
   422  	b.nodeAttrs = make(map[string]string, 4+len(n.Attributes)+len(n.Meta))
   423  	b.nodeAttrs[nodeIdKey] = n.ID
   424  	b.nodeAttrs[nodeNameKey] = n.Name
   425  	b.nodeAttrs[nodeClassKey] = n.NodeClass
   426  	b.nodeAttrs[nodeDcKey] = n.Datacenter
   427  	b.datacenter = n.Datacenter
   428  
   429  	// Set up the attributes.
   430  	for k, v := range n.Attributes {
   431  		b.nodeAttrs[fmt.Sprintf("%s%s", nodeAttributePrefix, k)] = v
   432  	}
   433  
   434  	// Set up the meta.
   435  	for k, v := range n.Meta {
   436  		b.nodeAttrs[fmt.Sprintf("%s%s", nodeMetaPrefix, k)] = v
   437  	}
   438  	return b
   439  }
   440  
   441  func (b *Builder) SetAllocDir(dir string) *Builder {
   442  	b.mu.Lock()
   443  	b.allocDir = dir
   444  	b.mu.Unlock()
   445  	return b
   446  }
   447  
   448  func (b *Builder) SetTaskLocalDir(dir string) *Builder {
   449  	b.mu.Lock()
   450  	b.localDir = dir
   451  	b.mu.Unlock()
   452  	return b
   453  }
   454  
   455  func (b *Builder) SetSecretsDir(dir string) *Builder {
   456  	b.mu.Lock()
   457  	b.secretsDir = dir
   458  	b.mu.Unlock()
   459  	return b
   460  }
   461  
   462  // SetDriverNetwork defined by the driver.
   463  func (b *Builder) SetDriverNetwork(n *cstructs.DriverNetwork) *Builder {
   464  	ncopy := n.Copy()
   465  	b.mu.Lock()
   466  	b.driverNetwork = ncopy
   467  	b.mu.Unlock()
   468  	return b
   469  }
   470  
   471  // buildNetworkEnv env vars in the given map.
   472  //
   473  //	Auto:   NOMAD_PORT_<label>
   474  //	Host:   NOMAD_IP_<label>, NOMAD_ADDR_<label>, NOMAD_HOST_PORT_<label>
   475  //
   476  // Handled by setAlloc -> otherPorts:
   477  //
   478  //	Task:   NOMAD_TASK_{IP,PORT,ADDR}_<task>_<label> # Always host values
   479  //
   480  func buildNetworkEnv(envMap map[string]string, nets structs.Networks, driverNet *cstructs.DriverNetwork) {
   481  	for _, n := range nets {
   482  		for _, p := range n.ReservedPorts {
   483  			buildPortEnv(envMap, p, n.IP, driverNet)
   484  		}
   485  		for _, p := range n.DynamicPorts {
   486  			buildPortEnv(envMap, p, n.IP, driverNet)
   487  		}
   488  	}
   489  }
   490  
   491  func buildPortEnv(envMap map[string]string, p structs.Port, ip string, driverNet *cstructs.DriverNetwork) {
   492  	// Host IP, port, and address
   493  	portStr := strconv.Itoa(p.Value)
   494  	envMap[IpPrefix+p.Label] = ip
   495  	envMap[HostPortPrefix+p.Label] = portStr
   496  	envMap[AddrPrefix+p.Label] = net.JoinHostPort(ip, portStr)
   497  
   498  	// Set Port to task's value if there's a port map
   499  	if driverNet != nil && driverNet.PortMap[p.Label] != 0 {
   500  		envMap[PortPrefix+p.Label] = strconv.Itoa(driverNet.PortMap[p.Label])
   501  	} else {
   502  		// Default to host's
   503  		envMap[PortPrefix+p.Label] = portStr
   504  	}
   505  }
   506  
   507  // SetHostEnvvars adds the host environment variables to the tasks. The filter
   508  // parameter can be use to filter host environment from entering the tasks.
   509  func (b *Builder) SetHostEnvvars(filter []string) *Builder {
   510  	filterMap := make(map[string]struct{}, len(filter))
   511  	for _, f := range filter {
   512  		filterMap[f] = struct{}{}
   513  	}
   514  
   515  	fullHostEnv := os.Environ()
   516  	filteredHostEnv := make(map[string]string, len(fullHostEnv))
   517  	for _, e := range fullHostEnv {
   518  		parts := strings.SplitN(e, "=", 2)
   519  		key, value := parts[0], parts[1]
   520  
   521  		// Skip filtered environment variables
   522  		if _, filtered := filterMap[key]; filtered {
   523  			continue
   524  		}
   525  
   526  		filteredHostEnv[key] = value
   527  	}
   528  
   529  	b.mu.Lock()
   530  	b.hostEnv = filteredHostEnv
   531  	b.mu.Unlock()
   532  	return b
   533  }
   534  
   535  func (b *Builder) SetTemplateEnv(m map[string]string) *Builder {
   536  	b.mu.Lock()
   537  	b.templateEnv = m
   538  	b.mu.Unlock()
   539  	return b
   540  }
   541  
   542  func (b *Builder) SetVaultToken(token string, inject bool) *Builder {
   543  	b.mu.Lock()
   544  	b.vaultToken = token
   545  	b.injectVaultToken = inject
   546  	b.mu.Unlock()
   547  	return b
   548  }
   549  
   550  // addPort keys and values for other tasks to an env var map
   551  func addPort(m map[string]string, taskName, ip, portLabel string, port int) {
   552  	key := fmt.Sprintf("%s%s_%s", AddrPrefix, taskName, portLabel)
   553  	m[key] = fmt.Sprintf("%s:%d", ip, port)
   554  	key = fmt.Sprintf("%s%s_%s", IpPrefix, taskName, portLabel)
   555  	m[key] = ip
   556  	key = fmt.Sprintf("%s%s_%s", PortPrefix, taskName, portLabel)
   557  	m[key] = strconv.Itoa(port)
   558  }