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

Code before:
	// path is the secret location used in Fetch
	path string
}

type Response struct {
	Result map[string]string `json:"result"`
	Error  *string           `json:"error,omitempty"`
}

func NewExternalPluginProvider(plugin commonplugins.SecretsPlugin, pluginName, secretName, path string) *ExternalPluginProvider {
	return &ExternalPluginProvider{
		plugin:     plugin,
		pluginName: pluginName,
		secretName: secretName,
		path:       path,
	}
}

// InterpolateEnv applies the given interpolation function to all environment
// variable values in rawEnv, then updates the underlying plugin with the
// interpolated values. This supports late interpolation of references like
// ${secret.X.Y} after template-based providers have resolved.
func (p *ExternalPluginProvider) InterpolateEnv(rawEnv map[string]string, interpolate func(string) string) {
	interpolated := make(map[string]string, len(rawEnv))
	for k, v := range rawEnv {
		interpolated[k] = interpolate(v)
	}
	p.plugin.SetEnv(interpolated)
}

func (p *ExternalPluginProvider) Fetch(ctx context.Context) (map[string]string, error) {
	resp, err := p.plugin.Fetch(ctx, p.path)
	if err != nil {
		return nil, fmt.Errorf("failed executing plugin %q for secret %q: %w", p.pluginName, p.secretName, err)
	}
	if resp.Error != nil {
		return nil, fmt.Errorf("provider %q for secret %q response contained error: %q", p.pluginName, p.secretName, *resp.Error)
	}

	formatted := make(map[string]string, len(resp.Result))
	for k, v := range resp.Result {
		formatted[fmt.Sprintf("secret.%s.%s", p.secretName, k)] = v
	}

	return formatted, nil
}


Review comment (suggestion):
Let's not interpolate the key. The reason here is because we do not want to accidentally allow overriding the `NOMAD_JOB_ID` or `NOMAD_NAMESPACE` variables.

Code after:
	// path is the secret location used in Fetch
	path string

	// env is the set of environment variables passed into plugin
	env map[string]string
}

type Response struct {
	Result map[string]string `json:"result"`
	Error  *string           `json:"error,omitempty"`
}

func NewExternalPluginProvider(plugin commonplugins.SecretsPlugin, pluginName, secretName, path string, env map[string]string) *ExternalPluginProvider {
	return &ExternalPluginProvider{
		plugin:     plugin,
		pluginName: pluginName,
		secretName: secretName,
		path:       path,
		env:        env,
	}
}

func (p *ExternalPluginProvider) InterpolateEnv(interpolate func(string) string) {
	for key, value := range p.env {
		p.env[key] = interpolate(value)
	}
}

func (p *ExternalPluginProvider) Fetch(ctx context.Context) (map[string]string, error) {
	resp, err := p.plugin.Fetch(ctx, p.path, p.env)
	if err != nil {
		return nil, fmt.Errorf("failed executing plugin %q for secret %q: %w", p.pluginName, p.secretName, err)
	}
	if resp.Error != nil {
		return nil, fmt.Errorf("provider %q for secret %q response contained error: %q", p.pluginName, p.secretName, *resp.Error)
	}

	formatted := make(map[string]string, len(resp.Result))
	for k, v := range resp.Result {
		formatted[fmt.Sprintf("secret.%s.%s", p.secretName, k)] = v
	}

	return formatted, nil
}