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  }