github.com/emate/nomad@v0.8.2-wo-binpacking/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  	b.taskMeta = make(map[string]string, len(combined)*2)
   401  	for k, v := range combined {
   402  		b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, strings.ToUpper(k))] = v
   403  		b.taskMeta[fmt.Sprintf("%s%s", MetaPrefix, k)] = v
   404  	}
   405  
   406  	// Add ports from other tasks
   407  	b.otherPorts = make(map[string]string, len(alloc.TaskResources)*2)
   408  	for taskName, resources := range alloc.TaskResources {
   409  		if taskName == b.taskName {
   410  			continue
   411  		}
   412  		for _, nw := range resources.Networks {
   413  			for _, p := range nw.ReservedPorts {
   414  				addPort(b.otherPorts, taskName, nw.IP, p.Label, p.Value)
   415  			}
   416  			for _, p := range nw.DynamicPorts {
   417  				addPort(b.otherPorts, taskName, nw.IP, p.Label, p.Value)
   418  			}
   419  		}
   420  	}
   421  	return b
   422  }
   423  
   424  // setNode is called from NewBuilder to populate node attributes.
   425  func (b *Builder) setNode(n *structs.Node) *Builder {
   426  	b.nodeAttrs = make(map[string]string, 4+len(n.Attributes)+len(n.Meta))
   427  	b.nodeAttrs[nodeIdKey] = n.ID
   428  	b.nodeAttrs[nodeNameKey] = n.Name
   429  	b.nodeAttrs[nodeClassKey] = n.NodeClass
   430  	b.nodeAttrs[nodeDcKey] = n.Datacenter
   431  	b.datacenter = n.Datacenter
   432  
   433  	// Set up the attributes.
   434  	for k, v := range n.Attributes {
   435  		b.nodeAttrs[fmt.Sprintf("%s%s", nodeAttributePrefix, k)] = v
   436  	}
   437  
   438  	// Set up the meta.
   439  	for k, v := range n.Meta {
   440  		b.nodeAttrs[fmt.Sprintf("%s%s", nodeMetaPrefix, k)] = v
   441  	}
   442  	return b
   443  }
   444  
   445  func (b *Builder) SetAllocDir(dir string) *Builder {
   446  	b.mu.Lock()
   447  	b.allocDir = dir
   448  	b.mu.Unlock()
   449  	return b
   450  }
   451  
   452  func (b *Builder) SetTaskLocalDir(dir string) *Builder {
   453  	b.mu.Lock()
   454  	b.localDir = dir
   455  	b.mu.Unlock()
   456  	return b
   457  }
   458  
   459  func (b *Builder) SetSecretsDir(dir string) *Builder {
   460  	b.mu.Lock()
   461  	b.secretsDir = dir
   462  	b.mu.Unlock()
   463  	return b
   464  }
   465  
   466  // SetDriverNetwork defined by the driver.
   467  func (b *Builder) SetDriverNetwork(n *cstructs.DriverNetwork) *Builder {
   468  	ncopy := n.Copy()
   469  	b.mu.Lock()
   470  	b.driverNetwork = ncopy
   471  	b.mu.Unlock()
   472  	return b
   473  }
   474  
   475  // buildNetworkEnv env vars in the given map.
   476  //
   477  //	Auto:   NOMAD_PORT_<label>
   478  //	Host:   NOMAD_IP_<label>, NOMAD_ADDR_<label>, NOMAD_HOST_PORT_<label>
   479  //
   480  // Handled by setAlloc -> otherPorts:
   481  //
   482  //	Task:   NOMAD_TASK_{IP,PORT,ADDR}_<task>_<label> # Always host values
   483  //
   484  func buildNetworkEnv(envMap map[string]string, nets structs.Networks, driverNet *cstructs.DriverNetwork) {
   485  	for _, n := range nets {
   486  		for _, p := range n.ReservedPorts {
   487  			buildPortEnv(envMap, p, n.IP, driverNet)
   488  		}
   489  		for _, p := range n.DynamicPorts {
   490  			buildPortEnv(envMap, p, n.IP, driverNet)
   491  		}
   492  	}
   493  }
   494  
   495  func buildPortEnv(envMap map[string]string, p structs.Port, ip string, driverNet *cstructs.DriverNetwork) {
   496  	// Host IP, port, and address
   497  	portStr := strconv.Itoa(p.Value)
   498  	envMap[IpPrefix+p.Label] = ip
   499  	envMap[HostPortPrefix+p.Label] = portStr
   500  	envMap[AddrPrefix+p.Label] = net.JoinHostPort(ip, portStr)
   501  
   502  	// Set Port to task's value if there's a port map
   503  	if driverNet != nil && driverNet.PortMap[p.Label] != 0 {
   504  		envMap[PortPrefix+p.Label] = strconv.Itoa(driverNet.PortMap[p.Label])
   505  	} else {
   506  		// Default to host's
   507  		envMap[PortPrefix+p.Label] = portStr
   508  	}
   509  }
   510  
   511  // SetHostEnvvars adds the host environment variables to the tasks. The filter
   512  // parameter can be use to filter host environment from entering the tasks.
   513  func (b *Builder) SetHostEnvvars(filter []string) *Builder {
   514  	filterMap := make(map[string]struct{}, len(filter))
   515  	for _, f := range filter {
   516  		filterMap[f] = struct{}{}
   517  	}
   518  
   519  	fullHostEnv := os.Environ()
   520  	filteredHostEnv := make(map[string]string, len(fullHostEnv))
   521  	for _, e := range fullHostEnv {
   522  		parts := strings.SplitN(e, "=", 2)
   523  		key, value := parts[0], parts[1]
   524  
   525  		// Skip filtered environment variables
   526  		if _, filtered := filterMap[key]; filtered {
   527  			continue
   528  		}
   529  
   530  		filteredHostEnv[key] = value
   531  	}
   532  
   533  	b.mu.Lock()
   534  	b.hostEnv = filteredHostEnv
   535  	b.mu.Unlock()
   536  	return b
   537  }
   538  
   539  func (b *Builder) SetTemplateEnv(m map[string]string) *Builder {
   540  	b.mu.Lock()
   541  	b.templateEnv = m
   542  	b.mu.Unlock()
   543  	return b
   544  }
   545  
   546  func (b *Builder) SetVaultToken(token string, inject bool) *Builder {
   547  	b.mu.Lock()
   548  	b.vaultToken = token
   549  	b.injectVaultToken = inject
   550  	b.mu.Unlock()
   551  	return b
   552  }
   553  
   554  // addPort keys and values for other tasks to an env var map
   555  func addPort(m map[string]string, taskName, ip, portLabel string, port int) {
   556  	key := fmt.Sprintf("%s%s_%s", AddrPrefix, taskName, portLabel)
   557  	m[key] = fmt.Sprintf("%s:%d", ip, port)
   558  	key = fmt.Sprintf("%s%s_%s", IpPrefix, taskName, portLabel)
   559  	m[key] = ip
   560  	key = fmt.Sprintf("%s%s_%s", PortPrefix, taskName, portLabel)
   561  	m[key] = strconv.Itoa(port)
   562  }