mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
secrets: Add secrets block to job spec (#26076)
This commit is contained in:
17
api/tasks.go
17
api/tasks.go
@@ -786,6 +786,7 @@ type Task struct {
|
|||||||
KillSignal string `mapstructure:"kill_signal" hcl:"kill_signal,optional"`
|
KillSignal string `mapstructure:"kill_signal" hcl:"kill_signal,optional"`
|
||||||
Kind string `hcl:"kind,optional"`
|
Kind string `hcl:"kind,optional"`
|
||||||
ScalingPolicies []*ScalingPolicy `hcl:"scaling,block"`
|
ScalingPolicies []*ScalingPolicy `hcl:"scaling,block"`
|
||||||
|
Secrets []*Secret `hcl:"secret,block"`
|
||||||
|
|
||||||
// Identity is the default Nomad Workload Identity and will be added to
|
// Identity is the default Nomad Workload Identity and will be added to
|
||||||
// Identities with the name "default"
|
// Identities with the name "default"
|
||||||
@@ -825,6 +826,9 @@ func (t *Task) Canonicalize(tg *TaskGroup, job *Job) {
|
|||||||
for _, tmpl := range t.Templates {
|
for _, tmpl := range t.Templates {
|
||||||
tmpl.Canonicalize()
|
tmpl.Canonicalize()
|
||||||
}
|
}
|
||||||
|
for _, s := range t.Secrets {
|
||||||
|
s.Canonicalize()
|
||||||
|
}
|
||||||
for _, s := range t.Services {
|
for _, s := range t.Services {
|
||||||
s.Canonicalize(t, tg, job)
|
s.Canonicalize(t, tg, job)
|
||||||
}
|
}
|
||||||
@@ -1042,6 +1046,19 @@ func (v *Vault) Canonicalize() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Secret struct {
|
||||||
|
Name string `hcl:"name,label"`
|
||||||
|
Provider string `hcl:"provider,optional"`
|
||||||
|
Path string `hcl:"path,optional"`
|
||||||
|
Config map[string]any `hcl:"config,block"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) Canonicalize() {
|
||||||
|
if len(s.Config) == 0 {
|
||||||
|
s.Config = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewTask creates and initializes a new Task.
|
// NewTask creates and initializes a new Task.
|
||||||
func NewTask(name, driver string) *Task {
|
func NewTask(name, driver string) *Task {
|
||||||
return &Task{
|
return &Task{
|
||||||
|
|||||||
@@ -506,6 +506,27 @@ func TestTask_Canonicalize_Vault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTask_Canonicalize_Secret(t *testing.T) {
|
||||||
|
testutil.Parallel(t)
|
||||||
|
|
||||||
|
testSecret := &Secret{
|
||||||
|
Name: "test-secret",
|
||||||
|
Provider: "test-provider",
|
||||||
|
Path: "/test/path",
|
||||||
|
Config: make(map[string]any),
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &Secret{
|
||||||
|
Name: "test-secret",
|
||||||
|
Provider: "test-provider",
|
||||||
|
Path: "/test/path",
|
||||||
|
Config: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
testSecret.Canonicalize()
|
||||||
|
must.Eq(t, expected, testSecret)
|
||||||
|
}
|
||||||
|
|
||||||
// Ensures no regression on https://github.com/hashicorp/nomad/issues/3132
|
// Ensures no regression on https://github.com/hashicorp/nomad/issues/3132
|
||||||
func TestTaskGroup_Canonicalize_Update(t *testing.T) {
|
func TestTaskGroup_Canonicalize_Update(t *testing.T) {
|
||||||
testutil.Parallel(t)
|
testutil.Parallel(t)
|
||||||
|
|||||||
@@ -1466,6 +1466,18 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(apiTask.Secrets) > 0 {
|
||||||
|
structsTask.Secrets = []*structs.Secret{}
|
||||||
|
for _, s := range apiTask.Secrets {
|
||||||
|
structsTask.Secrets = append(structsTask.Secrets, &structs.Secret{
|
||||||
|
Name: s.Name,
|
||||||
|
Provider: s.Provider,
|
||||||
|
Path: s.Path,
|
||||||
|
Config: s.Config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if apiTask.Consul != nil {
|
if apiTask.Consul != nil {
|
||||||
structsTask.Consul = apiConsulToStructs(apiTask.Consul)
|
structsTask.Consul = apiConsulToStructs(apiTask.Consul)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,8 @@ func decodeTaskGroup(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.D
|
|||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
|
|
||||||
tgExtra := struct {
|
tgExtra := struct {
|
||||||
Vault *api.Vault `hcl:"vault,block"`
|
Vault *api.Vault `hcl:"vault,block"`
|
||||||
|
Secrets []*api.Secret `hcl:"secret,block"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
extra, _ := gohcl.ImpliedBodySchema(tgExtra)
|
extra, _ := gohcl.ImpliedBodySchema(tgExtra)
|
||||||
@@ -286,6 +287,14 @@ func decodeTaskGroup(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.D
|
|||||||
diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, v)...)
|
diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, v)...)
|
||||||
tgExtra.Vault = v
|
tgExtra.Vault = v
|
||||||
}
|
}
|
||||||
|
if b.Type == "secret" {
|
||||||
|
v := &api.Secret{}
|
||||||
|
diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, v)...)
|
||||||
|
if len(b.Labels) == 1 {
|
||||||
|
v.Name = b.Labels[0]
|
||||||
|
}
|
||||||
|
tgExtra.Secrets = append(tgExtra.Secrets, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d := newHCLDecoder()
|
d := newHCLDecoder()
|
||||||
@@ -304,6 +313,16 @@ func decodeTaskGroup(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(tgExtra.Secrets) > 0 {
|
||||||
|
for _, t := range tg.Tasks {
|
||||||
|
if len(t.Secrets) == 0 {
|
||||||
|
t.Secrets = tgExtra.Secrets
|
||||||
|
} else {
|
||||||
|
t.Secrets = append(t.Secrets, t.Secrets...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if tg.Scaling != nil {
|
if tg.Scaling != nil {
|
||||||
if tg.Scaling.Type == "" {
|
if tg.Scaling.Type == "" {
|
||||||
tg.Scaling.Type = "horizontal"
|
tg.Scaling.Type = "horizontal"
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ func decode(c *jobConfig) error {
|
|||||||
diags = append(diags, decodeMapInterfaceType(&c.Job, c.EvalContext())...)
|
diags = append(diags, decodeMapInterfaceType(&c.Job, c.EvalContext())...)
|
||||||
diags = append(diags, decodeMapInterfaceType(&c.Tasks, c.EvalContext())...)
|
diags = append(diags, decodeMapInterfaceType(&c.Tasks, c.EvalContext())...)
|
||||||
diags = append(diags, decodeMapInterfaceType(&c.Vault, c.EvalContext())...)
|
diags = append(diags, decodeMapInterfaceType(&c.Vault, c.EvalContext())...)
|
||||||
|
diags = append(diags, decodeMapInterfaceType(&c.Secrets, c.EvalContext())...)
|
||||||
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags
|
return diags
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ func normalizeJob(jc *jobConfig) {
|
|||||||
t.Vault = jc.Vault
|
t.Vault = jc.Vault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(t.Secrets) == 0 {
|
||||||
|
t.Secrets = jc.Secrets
|
||||||
|
} else {
|
||||||
|
t.Secrets = append(t.Secrets, jc.Secrets...)
|
||||||
|
}
|
||||||
|
|
||||||
//COMPAT To preserve compatibility with pre-1.7 agents, move the default
|
//COMPAT To preserve compatibility with pre-1.7 agents, move the default
|
||||||
// identity to Task.Identity.
|
// identity to Task.Identity.
|
||||||
defaultIdx := -1
|
defaultIdx := -1
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const (
|
|||||||
localsLabel = "locals"
|
localsLabel = "locals"
|
||||||
vaultLabel = "vault"
|
vaultLabel = "vault"
|
||||||
taskLabel = "task"
|
taskLabel = "task"
|
||||||
|
secretLabel = "secret"
|
||||||
|
|
||||||
inputVariablesAccessor = "var"
|
inputVariablesAccessor = "var"
|
||||||
localsAccessor = "local"
|
localsAccessor = "local"
|
||||||
@@ -31,8 +32,9 @@ type jobConfig struct {
|
|||||||
|
|
||||||
ParseConfig *ParseConfig
|
ParseConfig *ParseConfig
|
||||||
|
|
||||||
Vault *api.Vault `hcl:"vault,block"`
|
Vault *api.Vault `hcl:"vault,block"`
|
||||||
Tasks []*api.Task `hcl:"task,block"`
|
Secrets []*api.Secret `hcl:"secret,block"`
|
||||||
|
Tasks []*api.Task `hcl:"task,block"`
|
||||||
|
|
||||||
InputVariables Variables
|
InputVariables Variables
|
||||||
LocalVariables Variables
|
LocalVariables Variables
|
||||||
@@ -174,6 +176,13 @@ func (c *jobConfig) decodeTopLevelExtras(content *hcl.BodyContent, ctx *hcl.Eval
|
|||||||
t.Name = b.Labels[0]
|
t.Name = b.Labels[0]
|
||||||
c.Tasks = append(c.Tasks, t)
|
c.Tasks = append(c.Tasks, t)
|
||||||
}
|
}
|
||||||
|
} else if b.Type == secretLabel {
|
||||||
|
t := &api.Secret{}
|
||||||
|
diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, t)...)
|
||||||
|
if len(b.Labels) == 1 {
|
||||||
|
t.Name = b.Labels[0]
|
||||||
|
c.Secrets = append(c.Secrets, t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +286,7 @@ func (c *jobConfig) decodeJob(content *hcl.BodyContent, ctx *hcl.EvalContext) hc
|
|||||||
extra, remain, mdiags := body.PartialContent(&hcl.BodySchema{
|
extra, remain, mdiags := body.PartialContent(&hcl.BodySchema{
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
Blocks: []hcl.BlockHeaderSchema{
|
||||||
{Type: "vault"},
|
{Type: "vault"},
|
||||||
|
{Type: "secret", LabelNames: []string{"name"}},
|
||||||
{Type: "task", LabelNames: []string{"name"}},
|
{Type: "task", LabelNames: []string{"name"}},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -537,6 +537,11 @@ func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) {
|
|||||||
diff.Objects = append(diff.Objects, vDiff)
|
diff.Objects = append(diff.Objects, vDiff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secDiffs := secretsDiffs(t.Secrets, other.Secrets, contextual)
|
||||||
|
if secDiffs != nil {
|
||||||
|
diff.Objects = append(diff.Objects, secDiffs...)
|
||||||
|
}
|
||||||
|
|
||||||
// Consul diff
|
// Consul diff
|
||||||
consulDiff := primitiveObjectDiff(t.Consul, other.Consul, nil, "Consul", contextual)
|
consulDiff := primitiveObjectDiff(t.Consul, other.Consul, nil, "Consul", contextual)
|
||||||
if consulDiff != nil {
|
if consulDiff != nil {
|
||||||
@@ -578,6 +583,61 @@ func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) {
|
|||||||
return diff, nil
|
return diff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func secretsDiff(old, new *Secret, contextual bool) *ObjectDiff {
|
||||||
|
diff := &ObjectDiff{Type: DiffTypeNone, Name: "Secret"}
|
||||||
|
if reflect.DeepEqual(old, new) {
|
||||||
|
return nil
|
||||||
|
} else if old == nil {
|
||||||
|
old = &Secret{}
|
||||||
|
diff.Type = DiffTypeAdded
|
||||||
|
} else if new == nil {
|
||||||
|
new = &Secret{}
|
||||||
|
diff.Type = DiffTypeDeleted
|
||||||
|
} else {
|
||||||
|
diff.Type = DiffTypeEdited
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff the primitive fields.
|
||||||
|
oldPrimitiveFlat := flatmap.Flatten(old, nil, false)
|
||||||
|
newPrimitiveFlat := flatmap.Flatten(new, nil, false)
|
||||||
|
diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
// secretsDiffs diffs a set of secrets. The comparator for whether a secret
|
||||||
|
// is new/edited/deleted is the secret Name field.
|
||||||
|
func secretsDiffs(old, new []*Secret, contextual bool) []*ObjectDiff {
|
||||||
|
var diffs []*ObjectDiff
|
||||||
|
|
||||||
|
oldMap := map[string]*Secret{}
|
||||||
|
newMap := map[string]*Secret{}
|
||||||
|
|
||||||
|
for _, o := range old {
|
||||||
|
oldMap[o.Name] = o
|
||||||
|
}
|
||||||
|
for _, n := range new {
|
||||||
|
newMap[n.Name] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range oldMap {
|
||||||
|
if diff := secretsDiff(v, newMap[k], contextual); diff != nil {
|
||||||
|
diffs = append(diffs, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range newMap {
|
||||||
|
// diff any newly added secrets
|
||||||
|
if _, ok := oldMap[k]; !ok {
|
||||||
|
if diff := secretsDiff(nil, v, contextual); diff != nil {
|
||||||
|
diffs = append(diffs, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(ObjectDiffs(diffs))
|
||||||
|
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
|
||||||
func actionDiff(old, new *Action, contextual bool) *ObjectDiff {
|
func actionDiff(old, new *Action, contextual bool) *ObjectDiff {
|
||||||
diff := &ObjectDiff{Type: DiffTypeNone, Name: "Action"}
|
diff := &ObjectDiff{Type: DiffTypeNone, Name: "Action"}
|
||||||
var oldPrimitiveFlat, newPrimitiveFlat map[string]string
|
var oldPrimitiveFlat, newPrimitiveFlat map[string]string
|
||||||
|
|||||||
@@ -9466,6 +9466,168 @@ func TestTaskDiff(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Secret edited",
|
||||||
|
Old: &Task{
|
||||||
|
Secrets: []*Secret{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Provider: "bar",
|
||||||
|
Path: "/foo/bar",
|
||||||
|
Config: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
New: &Task{
|
||||||
|
Secrets: []*Secret{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Provider: "bar1",
|
||||||
|
Path: "/foo/bar1",
|
||||||
|
Config: map[string]any{
|
||||||
|
"foo": "bar1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: &TaskDiff{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Objects: []*ObjectDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Name: "Secret",
|
||||||
|
Fields: []*FieldDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Name: "Config[foo]",
|
||||||
|
Old: "bar",
|
||||||
|
New: "bar1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Name: "Path",
|
||||||
|
Old: "/foo/bar",
|
||||||
|
New: "/foo/bar1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Name: "Provider",
|
||||||
|
Old: "bar",
|
||||||
|
New: "bar1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Secret added",
|
||||||
|
Old: &Task{
|
||||||
|
Secrets: []*Secret{},
|
||||||
|
},
|
||||||
|
New: &Task{
|
||||||
|
Secrets: []*Secret{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Provider: "bar",
|
||||||
|
Path: "/foo/bar",
|
||||||
|
Config: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: &TaskDiff{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Objects: []*ObjectDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeAdded,
|
||||||
|
Name: "Secret",
|
||||||
|
Fields: []*FieldDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeAdded,
|
||||||
|
Name: "Config[foo]",
|
||||||
|
Old: "",
|
||||||
|
New: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeAdded,
|
||||||
|
Name: "Name",
|
||||||
|
Old: "",
|
||||||
|
New: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeAdded,
|
||||||
|
Name: "Path",
|
||||||
|
Old: "",
|
||||||
|
New: "/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeAdded,
|
||||||
|
Name: "Provider",
|
||||||
|
Old: "",
|
||||||
|
New: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Secret deleted",
|
||||||
|
Old: &Task{
|
||||||
|
Secrets: []*Secret{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Provider: "bar",
|
||||||
|
Path: "/foo/bar",
|
||||||
|
Config: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
New: &Task{
|
||||||
|
Secrets: []*Secret{},
|
||||||
|
},
|
||||||
|
Expected: &TaskDiff{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Objects: []*ObjectDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeDeleted,
|
||||||
|
Name: "Secret",
|
||||||
|
Fields: []*FieldDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeDeleted,
|
||||||
|
Name: "Config[foo]",
|
||||||
|
Old: "bar",
|
||||||
|
New: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeDeleted,
|
||||||
|
Name: "Name",
|
||||||
|
Old: "foo",
|
||||||
|
New: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeDeleted,
|
||||||
|
Name: "Path",
|
||||||
|
Old: "/foo/bar",
|
||||||
|
New: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: DiffTypeDeleted,
|
||||||
|
Name: "Provider",
|
||||||
|
Old: "bar",
|
||||||
|
New: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
|||||||
@@ -7789,6 +7789,9 @@ type Task struct {
|
|||||||
// have access to.
|
// have access to.
|
||||||
Vault *Vault
|
Vault *Vault
|
||||||
|
|
||||||
|
// List of secrets for the task.
|
||||||
|
Secrets []*Secret
|
||||||
|
|
||||||
// Consul configuration specific to this task. If uset, falls back to the
|
// Consul configuration specific to this task. If uset, falls back to the
|
||||||
// group's Consul field.
|
// group's Consul field.
|
||||||
Consul *Consul
|
Consul *Consul
|
||||||
@@ -8288,6 +8291,19 @@ func (t *Task) Validate(jobType string, tg *TaskGroup) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secrets := make(map[string]bool)
|
||||||
|
for _, s := range t.Secrets {
|
||||||
|
if _, ok := secrets[s.Name]; ok {
|
||||||
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Duplicate secret %q found", s.Name))
|
||||||
|
} else {
|
||||||
|
secrets[s.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Validate(); err != nil {
|
||||||
|
mErr.Errors = append(mErr.Errors, fmt.Errorf("Secret %q is invalid: %w", s.Name, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mErr.ErrorOrNil()
|
return mErr.ErrorOrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10385,6 +10401,84 @@ func (v *Vault) Validate() error {
|
|||||||
return mErr.ErrorOrNil()
|
return mErr.ErrorOrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Secret struct {
|
||||||
|
Name string
|
||||||
|
Provider string
|
||||||
|
Path string
|
||||||
|
Config map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) Equal(o *Secret) bool {
|
||||||
|
if s == nil || o == nil {
|
||||||
|
return s == o
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case s.Name != o.Name:
|
||||||
|
return false
|
||||||
|
case s.Provider != o.Provider:
|
||||||
|
return false
|
||||||
|
case s.Path != o.Path:
|
||||||
|
return false
|
||||||
|
case !maps.Equal(s.Config, o.Config):
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) Copy() *Secret {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
confCopy, err := copystructure.Copy(s.Config)
|
||||||
|
if err != nil {
|
||||||
|
// The default Copy() implementation should not return
|
||||||
|
// an error, so we should not reach this code path.
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Secret{
|
||||||
|
Name: s.Name,
|
||||||
|
Provider: s.Provider,
|
||||||
|
Path: s.Path,
|
||||||
|
Config: confCopy.(map[string]any),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) Validate() error {
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var mErr multierror.Error
|
||||||
|
|
||||||
|
if s.Name == "" {
|
||||||
|
_ = multierror.Append(&mErr, fmt.Errorf("Secret name cannot be empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Provider == "" {
|
||||||
|
_ = multierror.Append(&mErr, fmt.Errorf("Secret provider cannot be empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Path == "" {
|
||||||
|
_ = multierror.Append(&mErr, fmt.Errorf("Secret path cannot be empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return mErr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) Canonicalize() {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.Config) == 0 {
|
||||||
|
s.Config = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DeploymentStatuses are the various states a deployment can be be in
|
// DeploymentStatuses are the various states a deployment can be be in
|
||||||
DeploymentStatusRunning = "running"
|
DeploymentStatusRunning = "running"
|
||||||
|
|||||||
@@ -6459,6 +6459,100 @@ func TestVault_Canonicalize(t *testing.T) {
|
|||||||
require.Equal(t, VaultChangeModeRestart, v.ChangeMode)
|
require.Equal(t, VaultChangeModeRestart, v.ChangeMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSecrets_Copy(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
s := &Secret{
|
||||||
|
Name: "test-secret",
|
||||||
|
Provider: "test-provider",
|
||||||
|
Path: "/test/path",
|
||||||
|
Config: map[string]any{
|
||||||
|
"some-key": map[string]any{
|
||||||
|
"nested-key": "nested-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ns := s.Copy()
|
||||||
|
|
||||||
|
must.Eq(t, s.Name, ns.Name)
|
||||||
|
must.Eq(t, s.Provider, ns.Provider)
|
||||||
|
must.Eq(t, s.Path, ns.Path)
|
||||||
|
must.Eq(t, s.Config, ns.Config)
|
||||||
|
|
||||||
|
// make sure nested maps are copied correctly
|
||||||
|
s.Config["some-key"].(map[string]any)["nested-key"] = "new-value"
|
||||||
|
|
||||||
|
must.NotEq(t, s.Config, ns.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecrets_Validate(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
secret *Secret
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid secret",
|
||||||
|
secret: &Secret{
|
||||||
|
Name: "test-secret",
|
||||||
|
Provider: "test-provier",
|
||||||
|
Path: "test-path",
|
||||||
|
},
|
||||||
|
expectErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing name",
|
||||||
|
secret: &Secret{
|
||||||
|
Path: "test-path",
|
||||||
|
Provider: "test-provider",
|
||||||
|
},
|
||||||
|
expectErr: fmt.Errorf("Secret name cannot be empty"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing provider",
|
||||||
|
secret: &Secret{
|
||||||
|
Name: "test-secret",
|
||||||
|
Path: "test-path",
|
||||||
|
},
|
||||||
|
expectErr: fmt.Errorf("Secret provider cannot be empty"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing path",
|
||||||
|
secret: &Secret{
|
||||||
|
Name: "test-secret",
|
||||||
|
Provider: "test-provier",
|
||||||
|
},
|
||||||
|
expectErr: fmt.Errorf("Secret path cannot be empty"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := tc.secret.Validate()
|
||||||
|
if tc.expectErr != nil {
|
||||||
|
must.ErrorContains(t, err, tc.expectErr.Error())
|
||||||
|
} else {
|
||||||
|
must.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecrets_Canonicalize(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
s := &Secret{
|
||||||
|
Name: "test-secret",
|
||||||
|
Provider: "test-provider",
|
||||||
|
Path: "/test/path",
|
||||||
|
Config: make(map[string]any),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Canonicalize()
|
||||||
|
|
||||||
|
must.Nil(t, s.Config)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParameterizedJobConfig_Validate(t *testing.T) {
|
func TestParameterizedJobConfig_Validate(t *testing.T) {
|
||||||
ci.Parallel(t)
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
|||||||
@@ -230,6 +230,9 @@ func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) comparison {
|
|||||||
if !at.Vault.Equal(bt.Vault) {
|
if !at.Vault.Equal(bt.Vault) {
|
||||||
return difference("task vault", at.Vault, bt.Vault)
|
return difference("task vault", at.Vault, bt.Vault)
|
||||||
}
|
}
|
||||||
|
if !slices.EqualFunc(at.Secrets, bt.Secrets, func(a, b *structs.Secret) bool { return a.Equal(b) }) {
|
||||||
|
return difference("task secrets", at.Secrets, bt.Secrets)
|
||||||
|
}
|
||||||
if c := consulUpdated(at.Consul, bt.Consul); c.modified {
|
if c := consulUpdated(at.Consul, bt.Consul); c.modified {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,6 +440,22 @@ func TestTasksUpdated(t *testing.T) {
|
|||||||
j32.TaskGroups[0].Tasks[0].VolumeMounts = nil
|
j32.TaskGroups[0].Tasks[0].VolumeMounts = nil
|
||||||
|
|
||||||
must.True(t, tasksUpdated(j31, j32, name).modified)
|
must.True(t, tasksUpdated(j31, j32, name).modified)
|
||||||
|
|
||||||
|
j33 := mock.Job()
|
||||||
|
j33 = j32.Copy()
|
||||||
|
|
||||||
|
must.False(t, tasksUpdated(j32, j33, name).modified)
|
||||||
|
|
||||||
|
// Add a task secret
|
||||||
|
j33.TaskGroups[0].Tasks[0].Secrets = append(j32.TaskGroups[0].Tasks[0].Secrets,
|
||||||
|
&structs.Secret{
|
||||||
|
Name: "mysecret",
|
||||||
|
Provider: "nomad",
|
||||||
|
Path: "/my/path",
|
||||||
|
})
|
||||||
|
|
||||||
|
must.True(t, tasksUpdated(j32, j33, name).modified)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTasksUpdated_connectServiceUpdated(t *testing.T) {
|
func TestTasksUpdated_connectServiceUpdated(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user