Add option to configure template job HCL file
Previously, the template job HCL file was hardcoded using go:embed in the binary. However, this did not allow users running Poseidon to change its content. Now, users can change the content of the template job HCL file using the configuration option.
This commit is contained in:

committed by
Tobias Kantusch

parent
12da813081
commit
67ebdbd650
@ -51,7 +51,11 @@ func initServer() *http.Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runnerManager := runner.NewNomadRunnerManager(nomadAPIClient, context.Background())
|
runnerManager := runner.NewNomadRunnerManager(nomadAPIClient, context.Background())
|
||||||
environmentManager := environment.NewNomadEnvironmentManager(runnerManager, nomadAPIClient)
|
environmentManager, err := environment.
|
||||||
|
NewNomadEnvironmentManager(runnerManager, nomadAPIClient, config.Config.Server.TemplateJobFile)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("Error initializing environment manager")
|
||||||
|
}
|
||||||
|
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Addr: config.Config.Server.URL().Host,
|
Addr: config.Config.Server.URL().Host,
|
||||||
|
@ -16,6 +16,8 @@ server:
|
|||||||
keyfile: ./poseidon.key
|
keyfile: ./poseidon.key
|
||||||
# If true, an additional WebSocket connection will be opened to split stdout and stderr when executing interactively
|
# If true, an additional WebSocket connection will be opened to split stdout and stderr when executing interactively
|
||||||
interactiveStderr: true
|
interactiveStderr: true
|
||||||
|
# If set, the file at the given path overwrites the default Nomad job file in internal/environment/template-environment-job.hcl
|
||||||
|
templateJobFile: ./poseidon.hcl
|
||||||
|
|
||||||
# Configuration of the used Nomad cluster
|
# Configuration of the used Nomad cluster
|
||||||
nomad:
|
nomad:
|
||||||
|
@ -28,6 +28,7 @@ var (
|
|||||||
KeyFile: "",
|
KeyFile: "",
|
||||||
},
|
},
|
||||||
InteractiveStderr: true,
|
InteractiveStderr: true,
|
||||||
|
TemplateJobFile: "",
|
||||||
},
|
},
|
||||||
Nomad: Nomad{
|
Nomad: Nomad{
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
@ -63,6 +64,7 @@ type server struct {
|
|||||||
Token string
|
Token string
|
||||||
TLS TLS
|
TLS TLS
|
||||||
InteractiveStderr bool
|
InteractiveStderr bool
|
||||||
|
TemplateJobFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL returns the URL of the Poseidon webserver.
|
// URL returns the URL of the Poseidon webserver.
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/runner"
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/runner"
|
||||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/dto"
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/dto"
|
||||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/logging"
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/logging"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,13 +40,35 @@ type Manager interface {
|
|||||||
func NewNomadEnvironmentManager(
|
func NewNomadEnvironmentManager(
|
||||||
runnerManager runner.Manager,
|
runnerManager runner.Manager,
|
||||||
apiClient nomad.ExecutorAPI,
|
apiClient nomad.ExecutorAPI,
|
||||||
) *NomadEnvironmentManager {
|
templateJobFile string,
|
||||||
m := &NomadEnvironmentManager{runnerManager, apiClient, *parseJob(templateEnvironmentJobHCL)}
|
) (*NomadEnvironmentManager, error) {
|
||||||
|
if err := loadTemplateEnvironmentJobHCL(templateJobFile); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
templateEnvironmentJob, err := parseJob(templateEnvironmentJobHCL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := &NomadEnvironmentManager{runnerManager, apiClient, *templateEnvironmentJob}
|
||||||
if err := m.Load(); err != nil {
|
if err := m.Load(); err != nil {
|
||||||
log.WithError(err).Error("Error recovering the execution environments")
|
log.WithError(err).Error("Error recovering the execution environments")
|
||||||
}
|
}
|
||||||
runnerManager.Load()
|
runnerManager.Load()
|
||||||
return m
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTemplateEnvironmentJobHCL loads the template environment job HCL from the given path.
|
||||||
|
// If the path is empty, the embedded default file is used.
|
||||||
|
func loadTemplateEnvironmentJobHCL(path string) error {
|
||||||
|
if path == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading template environment job: %w", err)
|
||||||
|
}
|
||||||
|
templateEnvironmentJobHCL = string(data)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type NomadEnvironmentManager struct {
|
type NomadEnvironmentManager struct {
|
||||||
@ -116,7 +139,7 @@ func (m *NomadEnvironmentManager) Load() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseJob(jobHCL string) *nomadApi.Job {
|
func parseJob(jobHCL string) (*nomadApi.Job, error) {
|
||||||
config := jobspec2.ParseConfig{
|
config := jobspec2.ParseConfig{
|
||||||
Body: []byte(jobHCL),
|
Body: []byte(jobHCL),
|
||||||
AllowFS: false,
|
AllowFS: false,
|
||||||
@ -124,9 +147,8 @@ func parseJob(jobHCL string) *nomadApi.Job {
|
|||||||
}
|
}
|
||||||
job, err := jobspec2.ParseWithConfig(&config)
|
job, err := jobspec2.ParseWithConfig(&config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Fatal("Error parsing Nomad job")
|
return nil, fmt.Errorf("error parsing Nomad job: %w", err)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return job
|
return job, nil
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,15 @@ package environment
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
nomadApi "github.com/hashicorp/nomad/api"
|
nomadApi "github.com/hashicorp/nomad/api"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/nomad"
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/nomad"
|
||||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/runner"
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/internal/runner"
|
||||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/dto"
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/pkg/dto"
|
||||||
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests"
|
"gitlab.hpi.de/codeocean/codemoon/poseidon/tests"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -119,24 +119,66 @@ func (s *CreateOrUpdateTestSuite) TestReturnsFalseIfCreatesOrUpdateEnvironmentRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseJob(t *testing.T) {
|
func TestParseJob(t *testing.T) {
|
||||||
exited := false
|
|
||||||
logger, hook := test.NewNullLogger()
|
|
||||||
logger.ExitFunc = func(i int) {
|
|
||||||
exited = true
|
|
||||||
}
|
|
||||||
|
|
||||||
log = logger.WithField("pkg", "nomad")
|
|
||||||
|
|
||||||
t.Run("parses the given default job", func(t *testing.T) {
|
t.Run("parses the given default job", func(t *testing.T) {
|
||||||
job := parseJob(templateEnvironmentJobHCL)
|
job, err := parseJob(templateEnvironmentJobHCL)
|
||||||
assert.False(t, exited)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, job)
|
assert.NotNil(t, job)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fatals when given wrong job", func(t *testing.T) {
|
t.Run("returns error when given wrong job", func(t *testing.T) {
|
||||||
job := parseJob("")
|
job, err := parseJob("")
|
||||||
assert.True(t, exited)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, job)
|
assert.Nil(t, job)
|
||||||
assert.Equal(t, logrus.FatalLevel, hook.LastEntry().Level)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewNomadEnvironmentManager(t *testing.T) {
|
||||||
|
executorAPIMock := &nomad.ExecutorAPIMock{}
|
||||||
|
executorAPIMock.On("LoadEnvironmentJobs").Return([]*nomadApi.Job{}, nil)
|
||||||
|
|
||||||
|
runnerManagerMock := &runner.ManagerMock{}
|
||||||
|
runnerManagerMock.On("Load").Return()
|
||||||
|
|
||||||
|
previousTemplateEnvironmentJobHCL := templateEnvironmentJobHCL
|
||||||
|
|
||||||
|
t.Run("returns error if template file does not exist", func(t *testing.T) {
|
||||||
|
_, err := NewNomadEnvironmentManager(runnerManagerMock, executorAPIMock, "/non-existent/file")
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("loads template environment job from file", func(t *testing.T) {
|
||||||
|
templateJobHCL := "job \"test\" {}"
|
||||||
|
expectedJob, err := parseJob(templateJobHCL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
f := createTempFile(t, templateJobHCL)
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
m, err := NewNomadEnvironmentManager(runnerManagerMock, executorAPIMock, f.Name())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, m)
|
||||||
|
assert.Equal(t, templateJobHCL, templateEnvironmentJobHCL)
|
||||||
|
assert.Equal(t, *expectedJob, m.templateEnvironmentJob)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns error if template file is invalid", func(t *testing.T) {
|
||||||
|
templateJobHCL := "invalid hcl file"
|
||||||
|
f := createTempFile(t, templateJobHCL)
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
_, err := NewNomadEnvironmentManager(runnerManagerMock, executorAPIMock, f.Name())
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
templateEnvironmentJobHCL = previousTemplateEnvironmentJobHCL
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTempFile(t *testing.T, content string) *os.File {
|
||||||
|
t.Helper()
|
||||||
|
f, err := os.CreateTemp("", "test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
n, err := f.WriteString(content)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, len(content), n)
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user