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 }