diff --git a/helper/args/args.go b/helper/args/args.go index 86f43f1f62e..fd78c377000 100644 --- a/helper/args/args.go +++ b/helper/args/args.go @@ -26,3 +26,8 @@ func ReplaceEnv(arg string, environments ...map[string]string) string { func ReplaceEnvWithPlaceHolder(arg string, placeholder string) string { return envRe.ReplaceAllString(arg, placeholder) } + +// ContainsEnv takes an arg and returns true if if contains an environment variable reference +func ContainsEnv(arg string) bool { + return envRe.MatchString(arg) +} diff --git a/helper/args/args_test.go b/helper/args/args_test.go index 974f4cbf39e..5b8d91b8264 100644 --- a/helper/args/args_test.go +++ b/helper/args/args_test.go @@ -75,3 +75,32 @@ func TestArgs_ReplaceEnv_Chained(t *testing.T) { t.Fatalf("ReplaceEnv(%v, %v) returned %#v; want %#v", input, envVars, act, exp) } } + +func TestArgs_ContainsEnv(t *testing.T) { + positiveCases := []string{ + "test-${env_var}", + } + for _, c := range positiveCases { + t.Run(fmt.Sprintf("positive case: %v", c), func(t *testing.T) { + if !ContainsEnv(c) { + t.Fatalf("ContainsEnv(%v) returned false; want true", c) + } + }) + } + + negativeCases := []string{ + "test", + "test-$", + "test-${asdf", + "test-{asdf}", + "$test", + } + for _, c := range negativeCases { + t.Run(fmt.Sprintf("negative case: %v", c), func(t *testing.T) { + if ContainsEnv(c) { + t.Fatalf("ContainsEnv(%v) returned true; want false", c) + } + }) + } + +} diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index f26df1d5c35..1f269c4c062 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -6154,50 +6154,62 @@ func (ta *TaskArtifact) Validate() error { mErr.Errors = append(mErr.Errors, fmt.Errorf("destination escapes allocation directory")) } - // Verify the checksum - if check, ok := ta.GetterOptions["checksum"]; ok { - check = strings.TrimSpace(check) - if check == "" { - mErr.Errors = append(mErr.Errors, fmt.Errorf("checksum value cannot be empty")) - return mErr.ErrorOrNil() - } + if err := ta.validateChecksum(); err != nil { + mErr.Errors = append(mErr.Errors, err) + } - parts := strings.Split(check, ":") - if l := len(parts); l != 2 { - mErr.Errors = append(mErr.Errors, fmt.Errorf(`checksum must be given as "type:value"; got %q`, check)) - return mErr.ErrorOrNil() - } + return mErr.ErrorOrNil() +} - checksumVal := parts[1] - checksumBytes, err := hex.DecodeString(checksumVal) - if err != nil { - mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid checksum: %v", err)) - return mErr.ErrorOrNil() - } +func (ta *TaskArtifact) validateChecksum() error { + check, ok := ta.GetterOptions["checksum"] + if !ok { + return nil + } - checksumType := parts[0] - expectedLength := 0 - switch checksumType { - case "md5": - expectedLength = md5.Size - case "sha1": - expectedLength = sha1.Size - case "sha256": - expectedLength = sha256.Size - case "sha512": - expectedLength = sha512.Size - default: - mErr.Errors = append(mErr.Errors, fmt.Errorf("unsupported checksum type: %s", checksumType)) - return mErr.ErrorOrNil() - } + // Job struct validation occurs before interpolation resolution can be effective. + // Skip checking if checksum contain variable reference, and artifacts fetching will + // eventually fail, if checksum is indeed invalid. + if args.ContainsEnv(check) { + return nil + } - if len(checksumBytes) != expectedLength { - mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid %s checksum: %v", checksumType, checksumVal)) - return mErr.ErrorOrNil() - } + check = strings.TrimSpace(check) + if check == "" { + return fmt.Errorf("checksum value cannot be empty") } - return mErr.ErrorOrNil() + parts := strings.Split(check, ":") + if l := len(parts); l != 2 { + return fmt.Errorf(`checksum must be given as "type:value"; got %q`, check) + } + + checksumVal := parts[1] + checksumBytes, err := hex.DecodeString(checksumVal) + if err != nil { + return fmt.Errorf("invalid checksum: %v", err) + } + + checksumType := parts[0] + expectedLength := 0 + switch checksumType { + case "md5": + expectedLength = md5.Size + case "sha1": + expectedLength = sha1.Size + case "sha256": + expectedLength = sha256.Size + case "sha512": + expectedLength = sha512.Size + default: + return fmt.Errorf("unsupported checksum type: %s", checksumType) + } + + if len(checksumBytes) != expectedLength { + return fmt.Errorf("invalid %s checksum: %v", checksumType, checksumVal) + } + + return nil } const ( diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 88bebcbe880..e6e9d09eba9 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -2672,6 +2672,15 @@ func TestTaskArtifact_Validate_Checksum(t *testing.T) { }, true, }, + { + &TaskArtifact{ + GetterSource: "foo.com", + GetterOptions: map[string]string{ + "checksum": "md5:${ARTIFACT_CHECKSUM}", + }, + }, + false, + }, } for i, tc := range cases {