| 1 | //go:build linux && rootrunner |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "encoding/xml" |
| 8 | "fmt" |
| 9 | "net/http" |
| 10 | "net/http/httptest" |
| 11 | "sort" |
| 12 | "strings" |
| 13 | "testing" |
| 14 | "time" |
| 15 | |
| 16 | "github.com/aws/aws-sdk-go-v2/aws" |
| 17 | "github.com/aws/aws-sdk-go-v2/service/s3" |
| 18 | "github.com/carlosframework/platform/internal/lease/leasetest" |
| 19 | ) |
| 20 | |
| 21 | func TestPlan14LifecycleS3(t *testing.T) { |
| 22 | model := leasetest.NewVersionedBucket() |
| 23 | model.AddBucket("data") |
| 24 | handler, _ := leasetest.NewS3Server(model) |
| 25 | server := httptest.NewServer(lifecycleS3(handler, model)) |
| 26 | defer server.Close() |
| 27 | lifecycleEnvironment(t, server.URL) |
| 28 | markers, err := newProductionClaimMarkers(t.Context(), "data", "eu-west-1") |
| 29 | if err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | key := "instances/lifecycle/generations/one/generation.json" |
| 33 | id, err := markers.PutMarker(t.Context(), "data", key, []byte("marker"), "", func(context.Context) error { return nil }) |
| 34 | if err != nil || id == "" { |
| 35 | t.Fatal(id, err) |
| 36 | } |
| 37 | listed, err := markers.(productionClaimMarkers).Client.ListObjectsV2(t.Context(), &s3.ListObjectsV2Input{Bucket: aws.String("data"), Prefix: aws.String("instances/")}) |
| 38 | if err != nil || len(listed.Contents) != 1 || aws.ToString(listed.Contents[0].Key) != key { |
| 39 | t.Fatal(listed, err) |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | func lifecycleS3(next http.Handler, model *leasetest.VersionedBucket) http.Handler { |
| 44 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 45 | q := r.URL.Query() |
| 46 | if r.Method != http.MethodGet || strings.Trim(r.URL.Path, "/") != "data" || q.Get("list-type") != "2" { |
| 47 | next.ServeHTTP(w, r) |
| 48 | return |
| 49 | } |
| 50 | versions, err := model.ListVersions(r.Context(), "data", q.Get("prefix")) |
| 51 | if err != nil { |
| 52 | http.Error(w, err.Error(), 500) |
| 53 | return |
| 54 | } |
| 55 | type object struct { |
| 56 | Key, LastModified, ETag string |
| 57 | Size int64 |
| 58 | } |
| 59 | type common struct{ Prefix string } |
| 60 | result := struct { |
| 61 | XMLName xml.Name `xml:"ListBucketResult"` |
| 62 | Name, Prefix, Delimiter string |
| 63 | MaxKeys, KeyCount int |
| 64 | IsTruncated bool |
| 65 | Contents []object |
| 66 | CommonPrefixes []common |
| 67 | }{Name: "data", Prefix: q.Get("prefix"), Delimiter: q.Get("delimiter"), MaxKeys: 1000} |
| 68 | prefixes := map[string]bool{} |
| 69 | for _, v := range versions { |
| 70 | if !v.IsLatest || v.DeleteMarker { |
| 71 | continue |
| 72 | } |
| 73 | if result.Delimiter != "" { |
| 74 | rest := strings.TrimPrefix(v.Key, result.Prefix) |
| 75 | if at := strings.Index(rest, result.Delimiter); at >= 0 { |
| 76 | prefixes[result.Prefix+rest[:at+len(result.Delimiter)]] = true |
| 77 | continue |
| 78 | } |
| 79 | } |
| 80 | result.Contents = append(result.Contents, object{v.Key, v.LastModified.UTC().Format(time.RFC3339Nano), fmt.Sprintf("%q", v.ETag), v.Size}) |
| 81 | } |
| 82 | for p := range prefixes { |
| 83 | result.CommonPrefixes = append(result.CommonPrefixes, common{p}) |
| 84 | } |
| 85 | sort.Slice(result.CommonPrefixes, func(i, j int) bool { return result.CommonPrefixes[i].Prefix < result.CommonPrefixes[j].Prefix }) |
| 86 | result.KeyCount = len(result.Contents) + len(result.CommonPrefixes) |
| 87 | if result.KeyCount > result.MaxKeys { |
| 88 | http.Error(w, "fixture listing exceeds one page", 500) |
| 89 | return |
| 90 | } |
| 91 | w.Header().Set("Content-Type", "application/xml") |
| 92 | xml.NewEncoder(w).Encode(result) |
| 93 | }) |
| 94 | } |
| 95 | |