github.com/kaisenlinux/docker@v0.0.0-20230510090727-ea55db55fac7/swarmkit/template/context.go (about)

     1  package template
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"text/template"
     8  
     9  	"github.com/docker/swarmkit/agent/configs"
    10  	"github.com/docker/swarmkit/agent/exec"
    11  	"github.com/docker/swarmkit/agent/secrets"
    12  	"github.com/docker/swarmkit/api"
    13  	"github.com/docker/swarmkit/api/naming"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // Platform holds information about the underlying platform of the node
    18  type Platform struct {
    19  	Architecture string
    20  	OS           string
    21  }
    22  
    23  // Context defines the strict set of values that can be injected into a
    24  // template expression in SwarmKit data structure.
    25  // NOTE: Be very careful adding any fields to this structure with types
    26  // that have methods defined on them. The template would be able to
    27  // invoke those methods.
    28  type Context struct {
    29  	Service struct {
    30  		ID     string
    31  		Name   string
    32  		Labels map[string]string
    33  	}
    34  
    35  	Node struct {
    36  		ID       string
    37  		Hostname string
    38  		Platform Platform
    39  	}
    40  
    41  	Task struct {
    42  		ID   string
    43  		Name string
    44  		Slot string
    45  
    46  		// NOTE(stevvooe): Why no labels here? Tasks don't actually have labels
    47  		// (from a user perspective). The labels are part of the container! If
    48  		// one wants to use labels for templating, use service labels!
    49  	}
    50  }
    51  
    52  // NewContext returns a new template context from the data available in the
    53  // task and the node where it is scheduled to run.
    54  // The provided context can then be used to populate runtime values in a
    55  // ContainerSpec.
    56  func NewContext(n *api.NodeDescription, t *api.Task) (ctx Context) {
    57  	ctx.Service.ID = t.ServiceID
    58  	ctx.Service.Name = t.ServiceAnnotations.Name
    59  	ctx.Service.Labels = t.ServiceAnnotations.Labels
    60  
    61  	ctx.Node.ID = t.NodeID
    62  
    63  	// Add node information to context only if we have them available
    64  	if n != nil {
    65  		ctx.Node.Hostname = n.Hostname
    66  		ctx.Node.Platform = Platform{
    67  			Architecture: n.Platform.Architecture,
    68  			OS:           n.Platform.OS,
    69  		}
    70  	}
    71  	ctx.Task.ID = t.ID
    72  	ctx.Task.Name = naming.Task(t)
    73  
    74  	if t.Slot != 0 {
    75  		ctx.Task.Slot = fmt.Sprint(t.Slot)
    76  	} else {
    77  		// fall back to node id for slot when there is no slot
    78  		ctx.Task.Slot = t.NodeID
    79  	}
    80  
    81  	return
    82  }
    83  
    84  // Expand treats the string s as a template and populates it with values from
    85  // the context.
    86  func (ctx *Context) Expand(s string) (string, error) {
    87  	tmpl, err := newTemplate(s, nil)
    88  	if err != nil {
    89  		return s, err
    90  	}
    91  
    92  	var buf bytes.Buffer
    93  	if err := tmpl.Execute(&buf, ctx); err != nil {
    94  		return s, err
    95  	}
    96  
    97  	return buf.String(), nil
    98  }
    99  
   100  // PayloadContext provides a context for expanding a config or secret payload.
   101  // NOTE: Be very careful adding any fields to this structure with types
   102  // that have methods defined on them. The template would be able to
   103  // invoke those methods.
   104  type PayloadContext struct {
   105  	Context
   106  
   107  	t                 *api.Task
   108  	restrictedSecrets exec.SecretGetter
   109  	restrictedConfigs exec.ConfigGetter
   110  	sensitive         bool
   111  }
   112  
   113  func (ctx *PayloadContext) secretGetter(target string) (string, error) {
   114  	if ctx.restrictedSecrets == nil {
   115  		return "", errors.New("secrets unavailable")
   116  	}
   117  
   118  	container := ctx.t.Spec.GetContainer()
   119  	if container == nil {
   120  		return "", errors.New("task is not a container")
   121  	}
   122  
   123  	for _, secretRef := range container.Secrets {
   124  		file := secretRef.GetFile()
   125  		if file != nil && file.Name == target {
   126  			secret, err := ctx.restrictedSecrets.Get(secretRef.SecretID)
   127  			if err != nil {
   128  				return "", err
   129  			}
   130  			ctx.sensitive = true
   131  			return string(secret.Spec.Data), nil
   132  		}
   133  	}
   134  
   135  	return "", errors.Errorf("secret target %s not found", target)
   136  }
   137  
   138  func (ctx *PayloadContext) configGetter(target string) (string, error) {
   139  	if ctx.restrictedConfigs == nil {
   140  		return "", errors.New("configs unavailable")
   141  	}
   142  
   143  	container := ctx.t.Spec.GetContainer()
   144  	if container == nil {
   145  		return "", errors.New("task is not a container")
   146  	}
   147  
   148  	for _, configRef := range container.Configs {
   149  		file := configRef.GetFile()
   150  		if file != nil && file.Name == target {
   151  			config, err := ctx.restrictedConfigs.Get(configRef.ConfigID)
   152  			if err != nil {
   153  				return "", err
   154  			}
   155  			return string(config.Spec.Data), nil
   156  		}
   157  	}
   158  
   159  	return "", errors.Errorf("config target %s not found", target)
   160  }
   161  
   162  func (ctx *PayloadContext) envGetter(variable string) (string, error) {
   163  	container := ctx.t.Spec.GetContainer()
   164  	if container == nil {
   165  		return "", errors.New("task is not a container")
   166  	}
   167  
   168  	for _, env := range container.Env {
   169  		parts := strings.SplitN(env, "=", 2)
   170  
   171  		if len(parts) > 1 && parts[0] == variable {
   172  			return parts[1], nil
   173  		}
   174  	}
   175  	return "", nil
   176  }
   177  
   178  // NewPayloadContextFromTask returns a new template context from the data
   179  // available in the task and the node where it is scheduled to run.
   180  // This context also provides access to the configs
   181  // and secrets that the task has access to. The provided context can then
   182  // be used to populate runtime values in a templated config or secret.
   183  func NewPayloadContextFromTask(node *api.NodeDescription, t *api.Task, dependencies exec.DependencyGetter) (ctx PayloadContext) {
   184  	return PayloadContext{
   185  		Context:           NewContext(node, t),
   186  		t:                 t,
   187  		restrictedSecrets: secrets.Restrict(dependencies.Secrets(), t),
   188  		restrictedConfigs: configs.Restrict(dependencies.Configs(), t),
   189  	}
   190  }
   191  
   192  // Expand treats the string s as a template and populates it with values from
   193  // the context.
   194  func (ctx *PayloadContext) Expand(s string) (string, error) {
   195  	funcMap := template.FuncMap{
   196  		"secret": ctx.secretGetter,
   197  		"config": ctx.configGetter,
   198  		"env":    ctx.envGetter,
   199  	}
   200  
   201  	tmpl, err := newTemplate(s, funcMap)
   202  	if err != nil {
   203  		return s, err
   204  	}
   205  
   206  	var buf bytes.Buffer
   207  	if err := tmpl.Execute(&buf, ctx); err != nil {
   208  		return s, err
   209  	}
   210  
   211  	return buf.String(), nil
   212  }