github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/api/headchef/headchef.go (about)

     1  package headchef
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"time"
     9  
    10  	"github.com/go-openapi/runtime"
    11  	httptransport "github.com/go-openapi/runtime/client"
    12  	"github.com/go-openapi/strfmt"
    13  
    14  	"github.com/ActiveState/cli/internal/errs"
    15  	"github.com/ActiveState/cli/internal/locale"
    16  	"github.com/ActiveState/cli/internal/logging"
    17  	"github.com/ActiveState/cli/pkg/platform/api"
    18  	"github.com/ActiveState/cli/pkg/platform/api/headchef/headchef_client"
    19  	"github.com/ActiveState/cli/pkg/platform/api/headchef/headchef_client/headchef_operations"
    20  	"github.com/ActiveState/cli/pkg/platform/api/headchef/headchef_models"
    21  	"github.com/ActiveState/cli/pkg/platform/authentication"
    22  )
    23  
    24  var (
    25  	ErrBuildResp        = errs.New("Build responded with error")
    26  	ErrBuildUnknownType = errs.New("Unknown build type")
    27  )
    28  
    29  type BuildStatus struct {
    30  	Started   chan *headchef_models.V1BuildStatusResponse
    31  	Failed    chan string
    32  	Completed chan *headchef_models.V1BuildStatusResponse
    33  	RunError  chan error
    34  }
    35  
    36  type BuildAnnotations struct {
    37  	CommitID     string `json:"commit_id"`
    38  	Project      string `json:"project"`
    39  	Organization string `json:"organization"`
    40  }
    41  
    42  func NewBuildStatus() *BuildStatus {
    43  	return &BuildStatus{
    44  		Started:   make(chan *headchef_models.V1BuildStatusResponse),
    45  		Failed:    make(chan string),
    46  		Completed: make(chan *headchef_models.V1BuildStatusResponse),
    47  		RunError:  make(chan error),
    48  	}
    49  }
    50  
    51  func (s *BuildStatus) Close() {
    52  	close(s.Started)
    53  	close(s.Failed)
    54  	close(s.Completed)
    55  	close(s.RunError)
    56  }
    57  
    58  type Client struct {
    59  	client    headchef_operations.Client
    60  	transport *httptransport.Runtime
    61  	auth      *authentication.Auth
    62  }
    63  
    64  func InitClient(auth *authentication.Auth) *Client {
    65  	return NewClient(api.GetServiceURL(api.ServiceHeadChef), auth)
    66  }
    67  
    68  func NewClient(apiURL *url.URL, auth *authentication.Auth) *Client {
    69  	logging.Debug("apiURL: %s", apiURL.String())
    70  	transportRuntime := httptransport.New(apiURL.Host, apiURL.Path, []string{apiURL.Scheme})
    71  	transportRuntime.Transport = api.NewRoundTripper(http.DefaultTransport)
    72  
    73  	// transportRuntime.SetDebug(true)
    74  
    75  	if auth != nil {
    76  		transportRuntime.DefaultAuthentication = auth.ClientAuth()
    77  	}
    78  
    79  	return &Client{
    80  		client:    *headchef_client.New(transportRuntime, strfmt.Default).HeadchefOperations,
    81  		transport: transportRuntime,
    82  		auth:      auth,
    83  	}
    84  }
    85  
    86  func (r *Client) RequestBuild(buildRequest *headchef_models.V1BuildRequest) *BuildStatus {
    87  	buildStatus := NewBuildStatus()
    88  
    89  	go func() {
    90  		defer buildStatus.Close()
    91  		r.reqBuild(buildRequest, buildStatus)
    92  	}()
    93  
    94  	return buildStatus
    95  }
    96  
    97  func (r *Client) RequestBuildSync(buildRequest *headchef_models.V1BuildRequest) (BuildStatusEnum, *headchef_models.V1BuildStatusResponse, error) {
    98  	return r.reqBuildSync(buildRequest)
    99  }
   100  
   101  func NewBuildRequest(recipeID, orgID, projID strfmt.UUID, annotations BuildAnnotations) (*headchef_models.V1BuildRequest, error) {
   102  	uid := strfmt.UUID("00010001-0001-0001-0001-000100010001")
   103  
   104  	br := &headchef_models.V1BuildRequest{
   105  		Requester: &headchef_models.V1BuildRequestRequester{
   106  			OrganizationID: &orgID,
   107  			ProjectID:      &projID,
   108  			UserID:         uid,
   109  		},
   110  		RecipeID:    recipeID,
   111  		Annotations: annotations,
   112  	}
   113  
   114  	return br, nil
   115  }
   116  
   117  type BuildParams struct {
   118  	headchef_operations.StartBuildV1Params
   119  	timeout      time.Duration
   120  	BuildRequest *headchef_models.V1BuildRequest
   121  }
   122  
   123  func (b *BuildParams) WithTimeout(timeout time.Duration) *BuildParams {
   124  	b.StartBuildV1Params.SetTimeout(timeout)
   125  	return b
   126  }
   127  
   128  func (b *BuildParams) SetTimeout(timeout time.Duration) {
   129  	b.timeout = timeout
   130  }
   131  
   132  func (b *BuildParams) WriteToRequest(req runtime.ClientRequest, reg strfmt.Registry) error {
   133  	if err := req.SetTimeout(b.timeout); err != nil {
   134  		return err
   135  	}
   136  
   137  	if b.BuildRequest != nil {
   138  		if err := req.SetBodyParam(b.BuildRequest); err != nil {
   139  			return err
   140  		}
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  type BuildStatusEnum int
   147  
   148  const (
   149  	Accepted BuildStatusEnum = iota
   150  	Started
   151  	Completed
   152  	Failed
   153  	Error
   154  )
   155  
   156  func (r *Client) reqBuildSync(buildReq *headchef_models.V1BuildRequest) (BuildStatusEnum, *headchef_models.V1BuildStatusResponse, error) {
   157  	startParams := headchef_operations.StartBuildV1Params{
   158  		Context:      context.Background(),
   159  		BuildRequest: buildReq,
   160  		HTTPClient:   api.NewHTTPClient(),
   161  	}
   162  
   163  	created, accepted, err := r.client.StartBuildV1(&startParams, r.auth.ClientAuth())
   164  
   165  	switch {
   166  	case err != nil:
   167  		msg := err.Error()
   168  		if startErr, ok := err.(*headchef_operations.StartBuildV1Default); ok {
   169  			msg = *startErr.Payload.Message
   170  		}
   171  		return Error, nil, errs.Wrap(ErrBuildResp, msg)
   172  	case accepted != nil:
   173  		return Accepted, accepted.Payload, nil
   174  	case created != nil:
   175  		if created.Payload.Type == nil {
   176  			requestBytes, err := buildReq.MarshalBinary()
   177  			if err != nil {
   178  				requestBytes = []byte(
   179  					fmt.Sprintf("cannot marshal request: %v", err),
   180  				)
   181  			}
   182  			msg := fmt.Sprintf(
   183  				"created response cannot be handled: nil type from request %q",
   184  				string(requestBytes),
   185  			)
   186  			return Error, nil, errs.New("Payload type was nil, message: %s", msg)
   187  		}
   188  		payloadType := *created.Payload.Type
   189  
   190  		switch payloadType {
   191  		case headchef_models.V1BuildStatusResponseTypeBuildCompleted:
   192  			return Completed, created.Payload, nil
   193  		case headchef_models.V1BuildStatusResponseTypeBuildFailed:
   194  			return Failed, created.Payload, nil
   195  		case headchef_models.V1BuildStatusResponseTypeBuildStarted:
   196  			return Started, created.Payload, nil
   197  		default:
   198  			msg := fmt.Sprintf(
   199  				"created response cannot be handled: unknown type %q",
   200  				payloadType,
   201  			)
   202  			return Error, nil, errs.Wrap(ErrBuildUnknownType, msg)
   203  		}
   204  	default:
   205  		return Error, nil, errs.New("no response")
   206  	}
   207  }
   208  
   209  func (r *Client) reqBuild(buildReq *headchef_models.V1BuildRequest, buildStatus *BuildStatus) {
   210  	startParams := headchef_operations.StartBuildV1Params{
   211  		Context:      context.Background(),
   212  		BuildRequest: buildReq,
   213  		HTTPClient:   api.NewHTTPClient(),
   214  	}
   215  
   216  	created, accepted, err := r.client.StartBuildV1(&startParams, r.auth.ClientAuth())
   217  
   218  	switch {
   219  	case err != nil:
   220  		msg := err.Error()
   221  		if startErr, ok := err.(*headchef_operations.StartBuildV1Default); ok {
   222  			msg = *startErr.Payload.Message
   223  		}
   224  		buildStatus.RunError <- locale.WrapError(ErrBuildResp, msg)
   225  	case accepted != nil:
   226  		buildStatus.Started <- accepted.Payload
   227  	case created != nil:
   228  		if created.Payload.Type == nil {
   229  			requestBytes, err := buildReq.MarshalBinary()
   230  			if err != nil {
   231  				requestBytes = []byte(
   232  					fmt.Sprintf("cannot marshal request: %v", err),
   233  				)
   234  			}
   235  			msg := fmt.Sprintf(
   236  				"created response cannot be handled: nil type from request %q",
   237  				string(requestBytes),
   238  			)
   239  			buildStatus.RunError <- errs.New("Payload type was nil, message: %s", msg)
   240  			break
   241  		}
   242  		payloadType := *created.Payload.Type
   243  
   244  		switch payloadType {
   245  		case headchef_models.V1BuildStatusResponseTypeBuildCompleted:
   246  			buildStatus.Completed <- created.Payload
   247  		case headchef_models.V1BuildStatusResponseTypeBuildFailed:
   248  			buildStatus.Failed <- created.Payload.Message
   249  		case headchef_models.V1BuildStatusResponseTypeBuildStarted:
   250  			buildStatus.Started <- created.Payload
   251  		default:
   252  			msg := fmt.Sprintf(
   253  				"created response cannot be handled: unknown type %q",
   254  				payloadType,
   255  			)
   256  			buildStatus.RunError <- locale.WrapError(ErrBuildUnknownType, msg)
   257  		}
   258  	default:
   259  		buildStatus.RunError <- errs.New("no response")
   260  	}
   261  }