github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/internal/codespaces/codespaces.go (about)

     1  package codespaces
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/cenkalti/backoff/v4"
    10  	"github.com/ungtb10d/cli/v2/internal/codespaces/api"
    11  	"github.com/ungtb10d/cli/v2/pkg/liveshare"
    12  )
    13  
    14  func connectionReady(codespace *api.Codespace) bool {
    15  	return codespace.Connection.SessionID != "" &&
    16  		codespace.Connection.SessionToken != "" &&
    17  		codespace.Connection.RelayEndpoint != "" &&
    18  		codespace.Connection.RelaySAS != "" &&
    19  		codespace.State == api.CodespaceStateAvailable
    20  }
    21  
    22  type apiClient interface {
    23  	GetCodespace(ctx context.Context, name string, includeConnection bool) (*api.Codespace, error)
    24  	StartCodespace(ctx context.Context, name string) error
    25  }
    26  
    27  type progressIndicator interface {
    28  	StartProgressIndicatorWithLabel(s string)
    29  	StopProgressIndicator()
    30  }
    31  
    32  type logger interface {
    33  	Println(v ...interface{})
    34  	Printf(f string, v ...interface{})
    35  }
    36  
    37  // ConnectToLiveshare waits for a Codespace to become running,
    38  // and connects to it using a Live Share session.
    39  func ConnectToLiveshare(ctx context.Context, progress progressIndicator, sessionLogger logger, apiClient apiClient, codespace *api.Codespace) (sess *liveshare.Session, err error) {
    40  	if codespace.State != api.CodespaceStateAvailable {
    41  		progress.StartProgressIndicatorWithLabel("Starting codespace")
    42  		defer progress.StopProgressIndicator()
    43  		if err := apiClient.StartCodespace(ctx, codespace.Name); err != nil {
    44  			return nil, fmt.Errorf("error starting codespace: %w", err)
    45  		}
    46  	}
    47  	expBackoff := backoff.NewExponentialBackOff()
    48  
    49  	expBackoff.Multiplier = 1.1
    50  	expBackoff.MaxInterval = 10 * time.Second
    51  	expBackoff.MaxElapsedTime = 5 * time.Minute
    52  
    53  	for retries := 0; !connectionReady(codespace); retries++ {
    54  		if retries > 1 {
    55  			duration := expBackoff.NextBackOff()
    56  			time.Sleep(duration)
    57  		}
    58  
    59  		if expBackoff.GetElapsedTime() >= expBackoff.MaxElapsedTime {
    60  			return nil, errors.New("timed out while waiting for the codespace to start")
    61  		}
    62  
    63  		codespace, err = apiClient.GetCodespace(ctx, codespace.Name, true)
    64  		if err != nil {
    65  			return nil, fmt.Errorf("error getting codespace: %w", err)
    66  		}
    67  	}
    68  
    69  	progress.StartProgressIndicatorWithLabel("Connecting to codespace")
    70  	defer progress.StopProgressIndicator()
    71  
    72  	return liveshare.Connect(ctx, liveshare.Options{
    73  		ClientName:     "gh",
    74  		SessionID:      codespace.Connection.SessionID,
    75  		SessionToken:   codespace.Connection.SessionToken,
    76  		RelaySAS:       codespace.Connection.RelaySAS,
    77  		RelayEndpoint:  codespace.Connection.RelayEndpoint,
    78  		HostPublicKeys: codespace.Connection.HostPublicKeys,
    79  		Logger:         sessionLogger,
    80  	})
    81  }