Repository: hashicorp/nomad
File: client/allocrunner/taskrunner/secrets_hook.go (Go)

Code before:
	tm, err := template.NewTaskTemplateManager(&template.TaskTemplateManagerConfig{
		UnblockCh:            unblock,
		Lifecycle:            h.lifecycle,
		Events:               h.events,
		Templates:            templates,
		ClientConfig:         h.clientConfig,
		VaultToken:           req.VaultToken,
		VaultConfig:          vaultConfig,
		VaultNamespace:       req.Alloc.Job.VaultNamespace,
		TaskDir:              req.TaskDir.Dir,
		EnvBuilder:           h.envBuilder,
		MaxTemplateEventRate: template.DefaultMaxTemplateEventRate,
		NomadNamespace:       h.nomadNamespace,
		NomadToken:           req.NomadToken,
		TaskID:               req.Alloc.ID + "-" + req.Task.Name,
		Logger:               h.logger,

		// This RenderFunc is used to keep any secret data from being written to disk.
		RenderFunc: func(ri *renderer.RenderInput) (*renderer.RenderResult, error) {
			// This append is not thread safe but during the initial render this
			// func is called by the tm.Run() goroutine synchronously. We may want
			// to lock this behind a mutex just in case that behavior changes.
			contents = append(contents, ri.Contents...)
			return &renderer.RenderResult{
				DidRender:   true,
				WouldRender: true,
				Contents:    ri.Contents,
			}, nil
		},
	})
	if err != nil {
		return err
	}

	go tm.Run()

	// Safeguard against the template manager continuing to run.
	defer tm.Stop()

	select {
	case <-ctx.Done():
		return nil
	case <-unblock:
	}

	// Set secrets from templates
	m, err := envparse.Parse(bytes.NewBuffer(contents))
	if err != nil {
		return err
	}
	h.envBuilder.SetSecrets(m)

Review comment (suggestion):
I started out using an `io.Pipe()` writer here, but the code was starting to get a bit hard to read/manage with blocking reads/writes in various goroutines.

This solution feels almost naively simplistic, but it works well with the way CT renders templates.  Let me know if I'm missing anything here.

The important thing to note is that there is only one goroutine appending to this slice, and we only read from it when that goroutine exits.

Code after:
	contents := []byte{}
	unblock := make(chan struct{})
	tm, err := template.NewTaskTemplateManager(&template.TaskTemplateManagerConfig{
		UnblockCh:            unblock,
		Lifecycle:            h.lifecycle,
		Events:               h.events,
		Templates:            templates,
		ClientConfig:         h.clientConfig,
		VaultToken:           req.VaultToken,
		VaultConfig:          vaultConfig,
		VaultNamespace:       req.Alloc.Job.VaultNamespace,
		TaskDir:              req.TaskDir.Dir,
		EnvBuilder:           h.envBuilder,
		MaxTemplateEventRate: template.DefaultMaxTemplateEventRate,
		NomadNamespace:       h.nomadNamespace,
		NomadToken:           req.NomadToken,
		TaskID:               req.Alloc.ID + "-" + req.Task.Name,
		Logger:               h.logger,

		// This RenderFunc is used to keep any secret data from being written to disk.
		RenderFunc: func(ri *renderer.RenderInput) (*renderer.RenderResult, error) {
			// This RenderFunc is called by a single goroutine synchronously, but we
			// lock the append in the event this behavior changes without us knowing.
			mu.Lock()
			defer mu.Unlock()
			contents = append(contents, ri.Contents...)
			return &renderer.RenderResult{
				DidRender:   true,
				WouldRender: true,
				Contents:    ri.Contents,
			}, nil
		},
	})
	if err != nil {
		return err
	}

	go tm.Run()

	// Safeguard against the template manager continuing to run.
	defer tm.Stop()

	select {
	case <-ctx.Done():
		return nil
	case <-unblock:
	}

	// Set secrets from templates
	m, err := envparse.Parse(bytes.NewBuffer(contents))
	if err != nil {