diff --git a/pkg/download_s3_archive.go b/pkg/download_s3_archive.go index 4cedf6b4..181ebcfa 100644 --- a/pkg/download_s3_archive.go +++ b/pkg/download_s3_archive.go @@ -17,8 +17,14 @@ import ( "github.com/qovery/qovery-cli/utils" ) -var uuidRegex = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`) -var uuidWithTimestampRegex = regexp.MustCompile(`^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-\d+$`) +const uuidPattern = `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}` + +// An archive is stored under a key made of a uuid, optionally followed by a numeric id. +// Execution ids reach us with a Unix timestamp appended on top of that key. Only a +// trailing 10 digit segment is a timestamp: a shorter one, such as the "-1211" in +// "-1211-1791383662", is part of the key itself and must be kept. +var executionIdWithTimestampRegex = regexp.MustCompile(`^(` + uuidPattern + `(?:-\d+)?)-\d{10}$`) +var executionIdRegex = regexp.MustCompile(`^` + uuidPattern + `(?:-\d+)?$`) type ArchiveTagsResponse struct { Key string @@ -30,15 +36,29 @@ type ArchiveResponse struct { Tags []ArchiveTagsResponse } +// normalizeExecutionId returns the key the archive is stored under, and whether the id +// is a shape we recognise. A trailing 10 digit Unix timestamp is dropped; any shorter +// numeric suffix belongs to the key and is kept. +func normalizeExecutionId(executionId string) (string, bool) { + if matches := executionIdWithTimestampRegex.FindStringSubmatch(executionId); matches != nil { + return matches[1], true + } + + return executionId, executionIdRegex.MatchString(executionId) +} + func DownloadS3Archive(executionId string, directory string) { - if matches := uuidWithTimestampRegex.FindStringSubmatch(executionId); matches != nil { - log.Warnf("Execution id '%s' contains a timestamp suffix, stripping it automatically", executionId) - executionId = matches[1] - } else if !uuidRegex.MatchString(executionId) { - log.Errorf("Invalid execution id format: '%s'. Expected a UUID (e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", executionId) + normalizedId, ok := normalizeExecutionId(executionId) + if !ok { + log.Errorf("Invalid execution id format: '%s'. Expected '' or '-', optionally followed by a '-'", executionId) return } + if normalizedId != executionId { + log.Warnf("Execution id '%s' ends with a timestamp, stripping it to '%s'", executionId, normalizedId) + executionId = normalizedId + } + fileName := executionId + ".tgz" res := download(utils.GetAdminUrl()+"/getS3ArchiveObject", fileName) diff --git a/pkg/download_s3_archive_test.go b/pkg/download_s3_archive_test.go new file mode 100644 index 00000000..ba28158d --- /dev/null +++ b/pkg/download_s3_archive_test.go @@ -0,0 +1,77 @@ +package pkg + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNormalizeExecutionId(t *testing.T) { + tests := []struct { + name string + input string + want string + valid bool + }{ + { + name: "timestamp trailing a suffixed key is stripped", + input: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-1211-1791383662", + want: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-1211", + valid: true, + }, + { + name: "a lone trailing timestamp is stripped down to the bare uuid", + input: "ef83256b-5df5-40f0-87a2-a30b62998836-1791386238", + want: "ef83256b-5df5-40f0-87a2-a30b62998836", + valid: true, + }, + { + name: "a suffix shorter than a timestamp is part of the key", + input: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-1211", + want: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-1211", + valid: true, + }, + { + name: "a bare uuid is kept", + input: "0781cf11-05c2-4844-8f4e-d72f73a37bfa", + want: "0781cf11-05c2-4844-8f4e-d72f73a37bfa", + valid: true, + }, + { + name: "not a uuid", + input: "not-an-execution-id", + valid: false, + }, + { + name: "uuid with a non numeric suffix", + input: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-abc", + valid: false, + }, + { + name: "uuid with an uppercase hex digit", + input: "0781CF11-05c2-4844-8f4e-d72f73a37bfa-1211", + valid: false, + }, + { + name: "a nine digit suffix is not a timestamp and is kept", + input: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-123456789", + want: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-123456789", + valid: true, + }, + { + name: "more suffixes than we know how to read", + input: "0781cf11-05c2-4844-8f4e-d72f73a37bfa-1211-1791383662-42", + valid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := normalizeExecutionId(tt.input) + assert.Equal(t, tt.valid, ok) + if tt.valid { + assert.Equal(t, tt.want, got) + } + }) + } +}