ronantakizawa/codereview-bench huggingface dataset:
Repository: hashicorp/nomad
File: command/job_start.go (Go)

Code before:
					c.Ui.Error(fmt.Sprintf("Error determining next launch time: %v", err))
				} else {
					c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
						formatTime(next), formatTimeDifference(now, next, time.Second)))
				}
			}
		} else if !paramjob {
			c.Ui.Output("Evaluation ID: " + resp.EvalID)
		}

		return 0
	}

	mon := newMonitor(c.Ui, client, length)
	return mon.monitor(resp.EvalID)
}

func parseFromSubmission(sub *api.JobSubmission) (*api.Job, error) {
	var job *api.Job
	var err error

	switch sub.Format {
	case "hcl2":
		job, err = jobspec2.Parse("", strings.NewReader(sub.Source))
		if err != nil {
			return nil, fmt.Errorf("command: unable to parce job submission: %w", err)
		}

	case "json":
		err = json.Unmarshal([]byte(sub.Source), &job)
		if err != nil {
			return nil, fmt.Errorf("command: unable to parce job submission: %w", err)
		}
	}

	return job, nil
}


Review comment (suggestion):
The error we return to the user here isn't going to explain why we're trying to parse the job submission in the first place. What if it was:

```suggestion
			return nil, fmt.Errorf("Unable to parse job submission to re-enable scaling policies: %w", err)
```

Code after:
			if err == nil {
				now := time.Now().In(loc)
				next, err := job.Periodic.Next(now)
				if err != nil {
					c.Ui.Error(fmt.Sprintf("Error determining next launch time: %v", err))
				} else {
					c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
						formatTime(next), formatTimeDifference(now, next, time.Second)))
				}
			}
		} else if !paramjob {
			c.Ui.Output("Evaluation ID: " + resp.EvalID)
		}

		return 0
	}

	mon := newMonitor(c.Ui, client, length)
	return mon.monitor(resp.EvalID)
}

func parseFromSubmission(sub *api.JobSubmission) (*api.Job, error) {
	var job *api.Job
	var err error

	switch sub.Format {
	case "hcl2":
		job, err = jobspec2.Parse("", strings.NewReader(sub.Source))
		if err != nil {
			return nil, fmt.Errorf("Unable to parse job submission to re-enable scaling policies: %w", err)
		}

	case "json":
		err = json.Unmarshal([]byte(sub.Source), &job)
		if err != nil {
			return nil, fmt.Errorf("Unable to parse job submission to re-enable scaling policies: %w", err)
		}
	}

	return job, nil
}
Repository: hashicorp/nomad
File: command/job_start.go (Go)

Code before:
		} else if !paramjob {
			c.Ui.Output("Evaluation ID: " + resp.EvalID)
		}

		return 0
	}

	mon := newMonitor(c.Ui, client, length)
	return mon.monitor(resp.EvalID)
}

func parseFromSubmission(sub *api.JobSubmission) (*api.Job, error) {
	var job *api.Job
	var err error

	switch sub.Format {
	case "hcl2":
		job, err = jobspec2.Parse("", strings.NewReader(sub.Source))
		if err != nil {
			return nil, fmt.Errorf("command: unable to parce job submission: %w", err)
		}

	case "json":
		err = json.Unmarshal([]byte(sub.Source), &job)
		if err != nil {
			return nil, fmt.Errorf("command: unable to parce job submission: %w", err)
		}
	}

	return job, nil
}


Review comment (suggestion):
```suggestion
			return nil, fmt.Errorf("Unable to parse job submission to re-enable scaling policies: %w", err)
```

Code after:
					c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
						formatTime(next), formatTimeDifference(now, next, time.Second)))
				}
			}
		} else if !paramjob {
			c.Ui.Output("Evaluation ID: " + resp.EvalID)
		}

		return 0
	}

	mon := newMonitor(c.Ui, client, length)
	return mon.monitor(resp.EvalID)
}

func parseFromSubmission(sub *api.JobSubmission) (*api.Job, error) {
	var job *api.Job
	var err error

	switch sub.Format {
	case "hcl2":
		job, err = jobspec2.Parse("", strings.NewReader(sub.Source))
		if err != nil {
			return nil, fmt.Errorf("Unable to parse job submission to re-enable scaling policies: %w", err)
		}

	case "json":
		err = json.Unmarshal([]byte(sub.Source), &job)
		if err != nil {
			return nil, fmt.Errorf("Unable to parse job submission to re-enable scaling policies: %w", err)
		}
	}

	return job, nil
}
Repository: hashicorp/nomad
File: nomad/fsm_test.go (Go)

Code before:
	if jobOut == nil {
		t.Fatalf("job not found!")
	}
	if !jobOut.Stop {
		t.Fatalf("job not stopped found!")
	}

	// Verify it was removed from the periodic runner.
	tuple := structs.NamespacedID{
		ID:        job.ID,
		Namespace: job.Namespace,
	}
	if _, ok := fsm.periodicDispatcher.tracked[tuple]; ok {
		t.Fatal("job not removed from periodic runner")
	}

	// Verify it was removed from the periodic launch table.
	launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Namespace, req.Job.ID)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if launchOut == nil {
		t.Fatalf("launch not found!")
	}

	//Verify the scaling policies where disabled
	for _, policy := range jobOut.GetScalingPolicies() {
		must.False(t, policy.Enabled)
	}
}

func TestFSM_BatchDeregisterJob(t *testing.T) {
	ci.Parallel(t)
	require := require.New(t)
	fsm := testFSM(t)

	job := mock.PeriodicJob()
	req := structs.JobRegisterRequest{
		Job: job,
		WriteRequest: structs.WriteRequest{
			Namespace: job.Namespace,
		},
	}
	buf, err := structs.Encode(structs.JobRegisterRequestType, req)
	require.Nil(err)
	resp := fsm.Apply(makeLog(buf))
	require.Nil(resp)

	job2 := mock.Job()
	req2 := structs.JobRegisterRequest{
		Job: job2,

Review comment (suggestion):
```suggestion
	// Verify the scaling policies were disabled
```

Code after:
	if jobOut == nil {
		t.Fatalf("job not found!")
	}
	if !jobOut.Stop {
		t.Fatalf("job not stopped found!")
	}

	// Verify it was removed from the periodic runner.
	tuple := structs.NamespacedID{
		ID:        job.ID,
		Namespace: job.Namespace,
	}
	if _, ok := fsm.periodicDispatcher.tracked[tuple]; ok {
		t.Fatal("job not removed from periodic runner")
	}

	// Verify it was removed from the periodic launch table.
	launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Namespace, req.Job.ID)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if launchOut == nil {
		t.Fatalf("launch not found!")
	}

	// Verify the scaling policies were disabled
	for _, policy := range jobOut.GetScalingPolicies() {
		must.False(t, policy.Enabled)
	}
}

func TestFSM_BatchDeregisterJob(t *testing.T) {
	ci.Parallel(t)
	require := require.New(t)
	fsm := testFSM(t)

	job := mock.PeriodicJob()
	req := structs.JobRegisterRequest{
		Job: job,
		WriteRequest: structs.WriteRequest{
			Namespace: job.Namespace,
		},
	}
	buf, err := structs.Encode(structs.JobRegisterRequestType, req)
	require.Nil(err)
	resp := fsm.Apply(makeLog(buf))
	require.Nil(resp)

	job2 := mock.Job()
	req2 := structs.JobRegisterRequest{
		Job: job2,
Repository: hashicorp/nomad
File: client/allocrunner/taskrunner/template_hook.go (Go)

Code before:
		h.nomadToken = req.NomadToken
	}

	tmpls := h.templateManager.Templates()

	// shutdown the old template
	h.templateManager.Stop()
	h.templateManager = nil

	err := h.renderTemplates(ctx, nil, tmpls)
	if err != nil {
		err = fmt.Errorf("failed to build template manager: %v", err)
		h.logger.Error("failed to build template manager", "error", err)
		_ = h.config.lifecycle.Kill(context.Background(),
			structs.NewTaskEvent(structs.TaskKilling).
				SetFailsTask().
				SetDisplayMessage(fmt.Sprintf("Template update %v", err)))
	}

	return nil
}

// renderTemplates creates the template managers and waits until each template has rendered, setting the watch
// templateManger on the hook when complete so it can be referenced during token updates.
func (h *templateHook) renderTemplates(ctx context.Context, once []*structs.Template, watch []*structs.Template) error {
	onceMgr, unblockOne, err := h.newManager(ctx, once)
	if err != nil {
		return err
	}

	watchMgr, unblockWatch, err := h.newManager(ctx, watch)
	if err != nil {
		return err
	}

	go onceMgr.Run()
	go watchMgr.Run()

	select {
	case <-ctx.Done():
	case <-unblockOne:
	}

	select {
	case <-ctx.Done():
	case <-unblockWatch:
	}

	// The template hook only needs to manage "watched" templates.
	// We can ignore the "once" manager after it's templates render.
	h.templateManager = watchMgr

Review comment (suggestion):
The context for `newManager` doesn't appear to be used? This is the context for the `Prestart` hook, so it doesn't look like we want to pass it into `Run` either, as we expect that goroutine to remain resident after `Prestart` (for the `watchMgr` case, at least).

Code after:
		h.nomadToken = req.NomadToken
	}

	tmpls := h.templateManager.Templates()

	// shutdown the old template
	h.templateManager.Stop()
	h.templateManager = nil

	err := h.renderTemplates(ctx, nil, tmpls)
	if err != nil {
		err = fmt.Errorf("failed to build template manager: %v", err)
		h.logger.Error("failed to build template manager", "error", err)
		_ = h.config.lifecycle.Kill(context.Background(),
			structs.NewTaskEvent(structs.TaskKilling).
				SetFailsTask().
				SetDisplayMessage(fmt.Sprintf("Template update %v", err)))
	}

	return nil
}

// renderTemplates creates the template managers and waits until each template has rendered, setting the watch
// templateManger on the hook when complete so it can be referenced during token updates.
func (h *templateHook) renderTemplates(ctx context.Context, once []*structs.Template, watch []*structs.Template) error {
	onceMgr, unblockOne, err := h.newManager(once)
	if err != nil {
		return err
	}

	watchMgr, unblockWatch, err := h.newManager(watch)
	if err != nil {
		return err
	}

	go onceMgr.Run()
	go watchMgr.Run()

	select {
	case <-ctx.Done():
		onceMgr.Stop()
	case <-unblockOne:
	}

	select {
	case <-ctx.Done():
		watchMgr.Stop()
	case <-unblockWatch:
	}

	// The template hook only needs to manage "watched" templates.
Repository: hashicorp/nomad
File: e2e/vaultcompat/vaultcompat_test.go (Go)

Code before:
			t.Run("jwt", func(t *testing.T) {
				testVaultJWT(t, b)
			})
		}

		// give nomad and vault time to stop
		defer func() { time.Sleep(5 * time.Second) }()
	})
}

func validateJWTAllocs(allocs []*nomadapi.AllocationListStub) error {
	if n := len(allocs); n != 2 {
		return fmt.Errorf("expected 2 allocs, got %d", n)
	}

	for _, alloc := range allocs {
		switch alloc.TaskGroup {

		// Verify all tasks in "success" group complete.
		case "success":
			if s := alloc.ClientStatus; s != "complete" {
				return fmt.Errorf("expected alloc status complete, got %s", s)
			}

		// Verify task in unauthorized
		case "fail_unauthorized":
			state, ok := alloc.TaskStates["unauthorized"]
			if !ok {
				return fmt.Errorf("expected task states for task: unauthorized")
			}
			hasEvent := false
			for _, ev := range state.Events {
				if strings.Contains(ev.DisplayMessage, "Missing: vault.read") {
					hasEvent = true
					break
				}
			}
			if !hasEvent {
				got := make([]string, 0, len(state.Events))
				for _, ev := range state.Events {
					got = append(got, ev.DisplayMessage)
				}
				return fmt.Errorf("missing expected event, got [%v]", strings.Join(got, ", "))
			}

		case "fail_missing":
			state, ok := alloc.TaskStates["missing_vault"]
			if !ok {
				return fmt.Errorf("expected task states for task: missing_vault")
			}
			if !state.Failed {

Review comment (bug):
This test used to have two tasks in a TaskGroup.  When one fails, the other is shutdown. This was fine when we were leaving the template runners running, but now that we are shutting them down when their context is cancelled, we need to test these two scenarios independently.

Code after:
			t.Run("jwt", func(t *testing.T) {
				testVaultJWT(t, b)
			})
		}

		// give nomad and vault time to stop
		defer func() { time.Sleep(5 * time.Second) }()
	})
}

func validateJWTAllocs(allocs []*nomadapi.AllocationListStub) error {
	if n := len(allocs); n != 3 {
		return fmt.Errorf("expected 3 allocs, got %d", n)
	}

	for _, alloc := range allocs {
		switch alloc.TaskGroup {

		// Verify all tasks in "success" group complete.
		case "success":
			if s := alloc.ClientStatus; s != "complete" {
				return fmt.Errorf("expected alloc status complete, got %s", s)
			}

		// Verify task in unauthorized
		case "fail_unauthorized":
			state, ok := alloc.TaskStates["unauthorized"]
			if !ok {
				return fmt.Errorf("expected task states for task: unauthorized")
			}
			hasEvent := false
			for _, ev := range state.Events {
				if strings.Contains(ev.DisplayMessage, "Missing: vault.read") {
					hasEvent = true
					break
				}
			}
			if !hasEvent {
				got := make([]string, 0, len(state.Events))
				for _, ev := range state.Events {
					got = append(got, ev.DisplayMessage)
				}
				return fmt.Errorf("missing expected event, got [%v]", strings.Join(got, ", "))
			}

		case "fail_missing":
			state, ok := alloc.TaskStates["missing_vault"]
			if !ok {
				return fmt.Errorf("expected task states for task: missing_vault")
			}
			if !state.Failed {
Repository: hashicorp/nomad
File: client/allocrunner/taskrunner/template/template.go (Go)

Code before:
	for _, tmpl := range config.Templates {
		if tmpl.ChangeSignal == "" {
			continue
		}

		sig, err := signals.Parse(tmpl.ChangeSignal)
		if err != nil {
			return nil, fmt.Errorf("Failed to parse signal %q", tmpl.ChangeSignal)
		}

		if tm.signals == nil {
			tm.signals = make(map[string]os.Signal)
		}

		tm.signals[tmpl.ChangeSignal] = sig
	}

	// Build the consul-template runner
	runner, lookup, err := templateRunner(config)
	if err != nil {
		return nil, err
	}
	tm.runner = runner
	tm.lookup = lookup

	go tm.run()
	return tm, nil
}

// Stop is used to stop the consul-template runner
func (tm *TaskTemplateManager) Stop() {
	tm.shutdownLock.Lock()
	defer tm.shutdownLock.Unlock()

	if tm.shutdown {
		return
	}

	close(tm.shutdownCh)
	tm.shutdown = true

	// Stop the consul-template runner
	if tm.runner != nil {
		tm.runner.Stop()
	}
}

// run is the long lived loop that handles errors and templates being rendered
func (tm *TaskTemplateManager) run() {
	// Runner is nil if there are no templates
	if tm.runner == nil {

Review comment (suggestion):
I know we had to do this because of the slight rearchitecture of the template hook, but I really like that we're not spawning a goroutine in the constructor anymore. It makes testing and reasoning about order-of-operations a lot easier. :+1: 

Code after:
	return len(c.Templates) > 0 && c.Templates[0].Once
}

func NewTaskTemplateManager(config *TaskTemplateManagerConfig) (*TaskTemplateManager, error) {
	// Check pre-conditions
	if err := config.Validate(); err != nil {
		return nil, err
	}

	tm := &TaskTemplateManager{
		config:     config,
		shutdownCh: make(chan struct{}),
	}

	// Parse the signals that we need
	for _, tmpl := range config.Templates {
		if tmpl.ChangeSignal == "" {
			continue
		}

		sig, err := signals.Parse(tmpl.ChangeSignal)
		if err != nil {
			return nil, fmt.Errorf("Failed to parse signal %q", tmpl.ChangeSignal)
		}

		if tm.signals == nil {
			tm.signals = make(map[string]os.Signal)
		}

		tm.signals[tmpl.ChangeSignal] = sig
	}

	// Build the consul-template runner
	runner, lookup, err := templateRunner(config)
	if err != nil {
		return nil, err
	}
	tm.runner = runner
	tm.lookup = lookup

	return tm, nil
}

// Stop is used to stop the consul-template runner
func (tm *TaskTemplateManager) Stop() {
	tm.shutdownLock.Lock()
	defer tm.shutdownLock.Unlock()

	if tm.shutdown {
		return
	}
Repository: hashicorp/nomad
File: client/allocrunner/taskrunner/template/template_test.go (Go)

Code before:
	must.NoError(t, err)
	must.True(t, bytes.HasSuffix(r, []byte("...done\n")), must.Sprint(string(r)))

	// Check that writeToFile was allowed
	path = filepath.Join(harness.taskDir, "writetofile.out")
	r, err = os.ReadFile(path)
	must.NoError(t, err)
	must.Eq(t, "hello", string(r))
}

func TestTaskTemplateManager_deniedSprig(t *testing.T) {
	ci.Parallel(t)

	file := "my.tmpl"
	template := &structs.Template{
		EmbeddedTmpl: `{{ "hello" | sprig_env }}`,
		DestPath:     file,
		ChangeMode:   structs.TemplateChangeModeNoop,
	}

	harness := newTestHarness(t, []*structs.Template{template}, false, false)

	must.NoError(t, harness.startWithErr(), must.Sprint("couldn't setup initial harness"))
	defer harness.stop()

	// Using sprig should cause a kill
	select {
	case <-harness.mockHooks.UnblockCh:
	case <-harness.mockHooks.EmitEventCh:
		t.Fatalf("Task event should not have been emitted")
	case e := <-harness.mockHooks.KillCh:
		must.StrContains(t, e.DisplayMessage, "not defined")
	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
		t.Fatalf("timeout")
	}

}


Review comment (suggestion):
```suggestion
	// Using sprig_env should cause a kill
```

Code after:
	must.NoError(t, err)
	must.True(t, bytes.HasSuffix(r, []byte("...done\n")), must.Sprint(string(r)))

	// Check that writeToFile was allowed
	path = filepath.Join(harness.taskDir, "writetofile.out")
	r, err = os.ReadFile(path)
	must.NoError(t, err)
	must.Eq(t, "hello", string(r))
}

func TestTaskTemplateManager_deniedSprig(t *testing.T) {
	ci.Parallel(t)

	file := "my.tmpl"
	template := &structs.Template{
		EmbeddedTmpl: `{{ "hello" | sprig_env }}`,
		DestPath:     file,
		ChangeMode:   structs.TemplateChangeModeNoop,
	}

	harness := newTestHarness(t, []*structs.Template{template}, false, false)

	must.NoError(t, harness.startWithErr(), must.Sprint("couldn't setup initial harness"))
	defer harness.stop()

	// Using sprig_env should cause a kill
	select {
	case <-harness.mockHooks.UnblockCh:
	case <-harness.mockHooks.EmitEventCh:
		t.Fatalf("Task event should not have been emitted")
	case e := <-harness.mockHooks.KillCh:
		must.StrContains(t, e.DisplayMessage, "not defined")
	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
		t.Fatalf("timeout")
	}

}
Repository: hashicorp/nomad
File: client/identity.go (Go)

Code before:
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package client

// NodeIdentityHandler is an interface that allows setting a node identity
// token. The client uses this to inform its subsystems about a new node
// identity that it should use for RPC calls.
type NodeIdentityHandler interface {
	SetNodeIdentityToken(token string)
}

// assertAndSetNodeIdentityToken safely asserts that the provided implementation
// satisfies the NodeIdentityHandler interface and calls SetNodeIdentityToken if
// it does.
func assertAndSetNodeIdentityToken(impl any, token string) {
	if handler, ok := impl.(NodeIdentityHandler); ok {
		handler.SetNodeIdentityToken(token)
	}
}


Review comment (bug):
We're doing a runtime check of something that's a programmer's error: the set of types where we'll call `SetNodeIdentityToken` is known at compile time. If we hit the `!ok` path then we're silently failing to set the identity. 

If we can't figure out a way to get the compiler to see that in the production code, we can make a type assertion like this in a test and it'll get checked when the tests compile:

```go
var _ NodeIdentityHandler = (*nsd.ServiceRegistrationHandler)(nil)
```

Code after:
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package client

// NodeIdentityHandler is an interface that allows setting a node identity
// token. The client uses this to inform its subsystems about a new node
// identity that it should use for RPC calls.
type NodeIdentityHandler interface {
	SetNodeIdentityToken(token string)
}

// assertAndSetNodeIdentityToken expects the passed interface implements
// NodeIdentityHandler and calls SetNodeIdentityToken. It is a programming error
// if the interface does not implement NodeIdentityHandler and will panic. The
// test file performs test assertions.
func assertAndSetNodeIdentityToken(impl any, token string) {
	if impl != nil {
		impl.(NodeIdentityHandler).SetNodeIdentityToken(token)
	}
}
Repository: hashicorp/nomad
File: client/allocrunner/taskrunner/secrets/nomad_provider.go (Go)

Code before:
	}

	if err := validateNomadInputs(conf, secret.Path); err != nil {
		return nil, err
	}

	return &NomadProvider{
		config:    conf,
		secret:    secret,
		secretDir: secretDir,
		tmplFile:  tmplFile,
	}, nil
}

func (n *NomadProvider) BuildTemplate() *structs.Template {
	data := fmt.Sprintf(`
		{{ with nomadVar "%s@%s" }}
		{{ range $k, $v := . }}
		secret.%s.{{ $k }}={{ $v }}
		{{ end }}
		{{ end }}`,
		n.secret.Path, n.config.Namespace, n.secret.Name)

	return &structs.Template{
		EmbeddedTmpl: data,
		DestPath:     filepath.Join(n.secretDir, n.tmplFile),
		ChangeMode:   structs.TemplateChangeModeNoop,
		Once:         true,
	}
}

func (n *NomadProvider) Parse() (map[string]string, error) {
	f, err := os.OpenInRoot(n.secretDir, n.tmplFile)
	if err != nil {
		return nil, fmt.Errorf("error opening env template: %v", err)
	}
	defer func() {
		f.Close()
		os.Remove(filepath.Join(n.secretDir, n.tmplFile))
	}()

	return envparse.Parse(f)
}

// validateNomadInputs ensures none of the user provided inputs contain delimiters
// that could be used to inject other CT functions.
func validateNomadInputs(conf *nomadProviderConfig, path string) error {
	// match if a string contains (...), {{, or }}
	var templateValidate = regexp.MustCompile(`\(.*\)|\{\{|\}\}`)

	if templateValidate.MatchString(conf.Namespace) {

Review comment (suggestion):
I'm not excited about doing multiple `filepath.Join()`'s but it's nice having the directory and filename separate for `os.OpenInRoot`. We could store this on the struct but that also feels like unnecessary duplication.

Code after:
		return nil, err
	}

	if err := validateNomadInputs(conf, secret.Path); err != nil {
		return nil, err
	}

	return &NomadProvider{
		config:    conf,
		secret:    secret,
		secretDir: secretDir,
		tmplFile:  tmplFile,
	}, nil
}

func (n *NomadProvider) BuildTemplate() *structs.Template {
	data := fmt.Sprintf(`
		{{ with nomadVar "%s@%s" }}
		{{ range $k, $v := . }}
		secret.%s.{{ $k }}={{ $v }}
		{{ end }}
		{{ end }}`,
		n.secret.Path, n.config.Namespace, n.secret.Name)

	return &structs.Template{
		EmbeddedTmpl: data,
		DestPath:     filepath.Clean(filepath.Join(n.secretDir, n.tmplFile)),
		ChangeMode:   structs.TemplateChangeModeNoop,
		Once:         true,
	}
}

func (n *NomadProvider) Parse() (map[string]string, error) {
	r, err := os.OpenRoot(n.secretDir)
	if err != nil {
		return nil, fmt.Errorf("error opening task secrets directory: %v", err)
	}
	defer r.Close()

	f, err := r.Open(n.tmplFile)
	if err != nil {
		return nil, fmt.Errorf("error opening env template: %v", err)
	}
	defer func() {
		f.Close()
		r.Remove(n.tmplFile)
	}()

	return envparse.Parse(f)
}