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  }