github.com/release-engineering/exodus-rsync@v1.11.2/internal/gw/publish.go (about) 1 package gw 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 8 "github.com/release-engineering/exodus-rsync/internal/log" 9 ) 10 11 type publish struct { 12 client *client 13 raw struct { 14 ID string 15 Env string 16 State string 17 Links map[string]string 18 } 19 } 20 21 // ItemInput is a single item accepted for publish by the AddItems method. 22 type ItemInput struct { 23 WebURI string `json:"web_uri"` 24 ObjectKey string `json:"object_key"` 25 ContentType string `json:"content_type"` 26 LinkTo string `json:"link_to"` 27 } 28 29 // NewPublish creates and returns a new publish object within exodus-gw. 30 func (c *client) NewPublish(ctx context.Context) (Publish, error) { 31 if c.dryRun { 32 return &dryRunPublish{}, nil 33 } 34 35 url := "/" + c.cfg.GwEnv() + "/publish" 36 37 out := &publish{} 38 headers := map[string][]string{"X-Idempotency-Key": {}} 39 if err := c.doJSONRequest(ctx, "POST", url, nil, &out.raw, headers); err != nil { 40 return out, err 41 } 42 43 out.client = c 44 45 return out, nil 46 } 47 48 func (c *client) GetPublish(ctx context.Context, id string) (Publish, error) { 49 if c.dryRun { 50 return &dryRunPublish{}, nil 51 } 52 53 url := "/" + c.cfg.GwEnv() + "/publish/" + id 54 55 out := &publish{} 56 out.client = c 57 58 // Make up the content of the publish object as we expect 59 // exodus-gw would have returned it. We do not actually know 60 // whether this is valid - we'll find out later when we try to 61 // use it. 62 out.raw.ID = id 63 out.raw.Env = c.cfg.GwEnv() 64 out.raw.Links = make(map[string]string) 65 out.raw.Links["self"] = url 66 out.raw.Links["commit"] = url + "/commit" 67 68 // Verify that the publish ID is valid before uploading blobs. 69 empty := struct{}{} 70 if err := c.doJSONRequest(ctx, "GET", url, nil, &empty, nil); err != nil { 71 return nil, err 72 } 73 74 return out, nil 75 } 76 77 func (p *publish) ID() string { 78 return p.raw.ID 79 } 80 81 // AddItems will add all of the specified items onto this publish. 82 // This may involve multiple requests to exodus-gw. 83 func (p *publish) AddItems(ctx context.Context, items []ItemInput) error { 84 c := p.client 85 url, ok := p.raw.Links["self"] 86 if !ok { 87 return fmt.Errorf("publish object is missing 'self' link: %+v", p.raw) 88 } 89 90 logger := log.FromContext(ctx) 91 92 var batch []ItemInput 93 batchSize := p.client.cfg.GwBatchSize() 94 95 nextBatch := func() { 96 if batchSize > len(items) { 97 batchSize = len(items) 98 } 99 batch = items[0:batchSize] 100 items = items[batchSize:] 101 } 102 103 count := 0 104 empty := struct{}{} 105 totalBatches := math.Ceil(float64(len(items)) / float64(batchSize)) 106 107 for nextBatch(); len(batch) > 0; nextBatch() { 108 count++ 109 // Log the current batch number at Info to serve as a gradual progress indicator. 110 logger.F("currentBatch", count, "totalBatches", totalBatches).Info("Preparing the next batch of items") 111 112 for _, item := range batch { 113 logger.F("item", item, "url", url).Debug("Adding to publish object") 114 } 115 116 headers := map[string][]string{"X-Idempotency-Key": {}} 117 err := c.doJSONRequest(ctx, "PUT", url, batch, &empty, headers) 118 if err != nil { 119 return err 120 } 121 } 122 123 return nil 124 } 125 126 // Commit will cause this publish object to become committed, making all of 127 // the included content available from the CDN. 128 // 129 // The commit operation within exodus-gw is asynchronous. This method will 130 // wait for the commit to complete fully and will return nil only if the 131 // commit has succeeded. 132 func (p *publish) Commit(ctx context.Context, mode string) error { 133 var err error 134 135 logger := log.FromContext(ctx) 136 defer logger.F("publish", p.ID(), "mode", mode).Trace("Committing publish").Stop(&err) 137 138 c := p.client 139 url, ok := p.raw.Links["commit"] 140 if !ok { 141 err = fmt.Errorf("publish not eligible for commit: %+v", p.raw) 142 return err 143 } 144 145 if mode != "" { 146 url = url + "?commit_mode=" + mode 147 } 148 149 task := task{} 150 headers := map[string][]string{"X-Idempotency-Key": {}} 151 if err := c.doJSONRequest(ctx, "POST", url, nil, &task.raw, headers); err != nil { 152 return err 153 } 154 155 task.client = c 156 157 err = task.Await(ctx) 158 return err 159 }