github.com/akamai/AkamaiOPEN-edgegrid-golang/v4@v4.1.0/pkg/papi/include_activations.go (about) 1 package papi 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "net/url" 10 11 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v4/pkg/edgegriderr" 12 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v4/pkg/tools" 13 validation "github.com/go-ozzo/ozzo-validation/v4" 14 ) 15 16 type ( 17 // IncludeActivations contains operations available on IncludeVersion resource 18 IncludeActivations interface { 19 // ActivateInclude creates a new include activation, which deactivates any current activation 20 // 21 // See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation 22 ActivateInclude(context.Context, ActivateIncludeRequest) (*ActivationIncludeResponse, error) 23 24 // DeactivateInclude deactivates the include activation 25 // 26 // See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation 27 DeactivateInclude(context.Context, DeactivateIncludeRequest) (*DeactivationIncludeResponse, error) 28 29 // CancelIncludeActivation cancels specified include activation, if it is still in `PENDING` state 30 // 31 // See: https://techdocs.akamai.com/property-mgr/reference/delete-include-activation 32 CancelIncludeActivation(context.Context, CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error) 33 34 // GetIncludeActivation gets details about an activation 35 // 36 // See: https://techdocs.akamai.com/property-mgr/reference/get-include-activation 37 GetIncludeActivation(context.Context, GetIncludeActivationRequest) (*GetIncludeActivationResponse, error) 38 39 // ListIncludeActivations lists all activations for all versions of the include, on both production and staging networks 40 // 41 // See: https://techdocs.akamai.com/property-mgr/reference/get-include-activations 42 ListIncludeActivations(context.Context, ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error) 43 } 44 45 // ActivateIncludeRequest contains parameters used to activate include 46 ActivateIncludeRequest ActivateOrDeactivateIncludeRequest 47 48 // DeactivateIncludeRequest contains parameters used to deactivate include 49 DeactivateIncludeRequest ActivateOrDeactivateIncludeRequest 50 51 // ActivateOrDeactivateIncludeRequest contains parameters used to activate or deactivate include 52 ActivateOrDeactivateIncludeRequest struct { 53 IncludeID string `json:"-"` 54 Version int `json:"includeVersion"` 55 Network ActivationNetwork `json:"network"` 56 Note string `json:"note"` 57 NotifyEmails []string `json:"notifyEmails"` 58 AcknowledgeWarnings []string `json:"acknowledgeWarnings,omitempty"` 59 AcknowledgeAllWarnings bool `json:"acknowledgeAllWarnings"` 60 IgnoreHTTPErrors *bool `json:"ignoreHttpErrors,omitempty"` 61 ComplianceRecord complianceRecord `json:"complianceRecord,omitempty"` 62 } 63 64 // CancelIncludeActivationRequest contains parameters used to cancel pending activation of include 65 CancelIncludeActivationRequest struct { 66 ContractID string 67 GroupID string 68 IncludeID string 69 ActivationID string 70 } 71 72 // CancelIncludeActivationResponse represents a response object returned by CancelIncludeActivation operation 73 CancelIncludeActivationResponse ListIncludeActivationsResponse 74 75 // ActivationIncludeResponse represents a response object returned by ActivateInclude operation 76 ActivationIncludeResponse struct { 77 ActivationID string `json:"-"` 78 ActivationLink string `json:"activationLink"` 79 } 80 81 // DeactivationIncludeResponse represents a response object returned by DeactivateInclude operation 82 DeactivationIncludeResponse struct { 83 ActivationID string `json:"-"` 84 ActivationLink string `json:"activationLink"` 85 } 86 87 // GetIncludeActivationRequest contains parameters used to get the include activation 88 GetIncludeActivationRequest struct { 89 IncludeID string 90 ActivationID string 91 } 92 93 // GetIncludeActivationResponse represents a response object returned by GetIncludeActivation 94 GetIncludeActivationResponse struct { 95 AccountID string `json:"accountId"` 96 ContractID string `json:"contractId"` 97 GroupID string `json:"groupId"` 98 Activations IncludeActivationsRes `json:"activations"` 99 Validations *Validations `json:"validations,omitempty"` 100 Activation IncludeActivation `json:"-"` 101 } 102 103 // Validations represent include activation validation object 104 Validations struct { 105 ValidationSummary ValidationSummary `json:"validationSummary"` 106 ValidationProgressItemList ValidationProgress `json:"validationProgressItemList"` 107 Network ActivationNetwork `json:"network"` 108 } 109 110 // ValidationSummary represent include activation validation summary object 111 ValidationSummary struct { 112 CompletePercent float64 `json:"completePercent"` 113 HasValidationError bool `json:"hasValidationError"` 114 HasValidationWarning bool `json:"hasValidationWarning"` 115 HasSystemError bool `json:"hasSystemError"` 116 HasClientError bool `json:"hasClientError"` 117 MessageState string `json:"messageState"` 118 } 119 120 // ValidationProgress represents include activation validation progress object 121 ValidationProgress struct { 122 ErrorItems []ErrorItem `json:"errorItemsList"` 123 } 124 125 // ErrorItem represents validation progress error item object 126 ErrorItem struct { 127 VersionID int `json:"versionId"` 128 PropertyName string `json:"propertyName"` 129 VersionNumber int `json:"versionNumber"` 130 HasValidationError bool `json:"hasValidationError"` 131 HasValidationWarning bool `json:"hasValidationWarning"` 132 ValidationResultsLink string `json:"validationResultsLink"` 133 } 134 135 // ListIncludeActivationsRequest contains parameters used to list the include activations 136 ListIncludeActivationsRequest struct { 137 IncludeID string 138 ContractID string 139 GroupID string 140 } 141 142 // ListIncludeActivationsResponse represents a response object returned by ListIncludeActivations 143 ListIncludeActivationsResponse struct { 144 AccountID string `json:"accountId"` 145 ContractID string `json:"contractId"` 146 GroupID string `json:"groupId"` 147 Activations IncludeActivationsRes `json:"activations"` 148 } 149 150 // IncludeActivationsRes represents Activations object 151 IncludeActivationsRes struct { 152 Items []IncludeActivation `json:"items"` 153 } 154 155 // IncludeActivation represents an include activation object 156 IncludeActivation struct { 157 ActivationID string `json:"activationId"` 158 Network ActivationNetwork `json:"network"` 159 ActivationType ActivationType `json:"activationType"` 160 Status ActivationStatus `json:"status"` 161 SubmitDate string `json:"submitDate"` 162 UpdateDate string `json:"updateDate"` 163 Note string `json:"note"` 164 NotifyEmails []string `json:"notifyEmails"` 165 FMAActivationState string `json:"fmaActivationState"` 166 FallbackInfo *ActivationFallbackInfo `json:"fallbackInfo"` 167 IncludeID string `json:"includeId"` 168 IncludeName string `json:"includeName"` 169 IncludeType IncludeType `json:"includeType"` 170 IncludeVersion int `json:"includeVersion"` 171 IncludeActivationID string `json:"includeActivationId"` 172 } 173 174 // complianceRecord is an interface for ComplianceRecord data type 175 complianceRecord interface { 176 noncomplianceReasonType() string 177 } 178 179 // ComplianceRecordNone holds data relevant for ComplianceRecord with noncomplianceReason 'None' 180 ComplianceRecordNone struct { 181 CustomerEmail string `json:"customerEmail"` 182 PeerReviewedBy string `json:"peerReviewedBy"` 183 UnitTested bool `json:"unitTested"` 184 TicketID string `json:"ticketId,omitempty"` 185 } 186 187 // ComplianceRecordOther holds data relevant for ComplianceRecord with noncomplianceReason 'Other' 188 ComplianceRecordOther struct { 189 OtherNoncomplianceReason string `json:"otherNoncomplianceReason"` 190 TicketID string `json:"ticketId,omitempty"` 191 } 192 193 // ComplianceRecordNoProductionTraffic holds data relevant for ComplianceRecord with noncomplianceReason 'NoProductionTraffic' 194 ComplianceRecordNoProductionTraffic struct { 195 TicketID string `json:"ticketId,omitempty"` 196 } 197 198 // ComplianceRecordEmergency holds data relevant for ComplianceRecord with noncomplianceReason 'Emergency' 199 ComplianceRecordEmergency struct { 200 TicketID string `json:"ticketId,omitempty"` 201 } 202 ) 203 204 const ( 205 // NoncomplianceReasonNoProductionTraffic is noncompliance reason type for compliance record 206 NoncomplianceReasonNoProductionTraffic = "NO_PRODUCTION_TRAFFIC" 207 // NoncomplianceReasonOther is noncompliance reason type for compliance record 208 NoncomplianceReasonOther = "OTHER" 209 // NoncomplianceReasonEmergency is noncompliance reason type for compliance record 210 NoncomplianceReasonEmergency = "EMERGENCY" 211 // NoncomplianceReasonNone is noncompliance reason type for compliance record 212 NoncomplianceReasonNone = "NONE" 213 ) 214 215 // Validate validates ActivateIncludeRequest 216 func (i ActivateIncludeRequest) Validate() error { 217 return edgegriderr.ParseValidationErrors(validation.Errors{ 218 "IncludeID": validation.Validate(i.IncludeID, validation.Required), 219 "Version": validation.Validate(i.Version, validation.Required), 220 "Network": validation.Validate(i.Network, validation.Required), 221 "NotifyEmails": validation.Validate(i.NotifyEmails, validation.Required), 222 "ComplianceRecord": validation.Validate(i.ComplianceRecord, 223 validation.Required.When(i.Network == ActivationNetworkProduction). 224 Error("ComplianceRecord is required for production network"), 225 validation.When(i.Network == ActivationNetworkProduction, validation.By(unitTestedFieldValidationRule))), 226 }) 227 } 228 229 func unitTestedFieldValidationRule(value interface{}) error { 230 switch value.(type) { 231 case *ComplianceRecordNone: 232 if value.(*ComplianceRecordNone).UnitTested == false { 233 return errors.New("for PRODUCTION activation network and nonComplianceRecord, UnitTested value has to be set to true, otherwise API will not work correctly") 234 } 235 } 236 return nil 237 } 238 239 func (c *ComplianceRecordNone) noncomplianceReasonType() string { 240 return NoncomplianceReasonNone 241 } 242 243 // Validate validates ComplianceRecordNone 244 func (c *ComplianceRecordNone) Validate() error { 245 return validation.Errors{ 246 "CustomerEmail": validation.Validate(c.CustomerEmail, validation.Required), 247 "PeerReviewedBy": validation.Validate(c.PeerReviewedBy, validation.Required), 248 }.Filter() 249 } 250 251 // MarshalJSON is a custom marshaller for ComplianceRecordNone struct 252 func (c ComplianceRecordNone) MarshalJSON() ([]byte, error) { 253 type ComplianceRecord ComplianceRecordNone 254 v := struct { 255 ComplianceRecord 256 NoncomplianceReason string `json:"noncomplianceReason"` 257 }{ 258 ComplianceRecord(c), 259 c.noncomplianceReasonType(), 260 } 261 return json.Marshal(v) 262 } 263 264 func (c *ComplianceRecordOther) noncomplianceReasonType() string { 265 return NoncomplianceReasonOther 266 } 267 268 // Validate validates ComplianceRecordOther 269 func (c *ComplianceRecordOther) Validate() error { 270 return validation.Errors{ 271 "OtherNoncomplianceReason": validation.Validate(c.OtherNoncomplianceReason, validation.Required), 272 }.Filter() 273 } 274 275 // MarshalJSON is a custom marshaller for ComplianceRecordOther struct 276 func (c ComplianceRecordOther) MarshalJSON() ([]byte, error) { 277 type ComplianceRecord ComplianceRecordOther 278 v := struct { 279 ComplianceRecord 280 NoncomplianceReason string `json:"noncomplianceReason"` 281 }{ 282 ComplianceRecord(c), 283 c.noncomplianceReasonType(), 284 } 285 return json.Marshal(v) 286 } 287 288 func (c *ComplianceRecordNoProductionTraffic) noncomplianceReasonType() string { 289 return NoncomplianceReasonNoProductionTraffic 290 } 291 292 // MarshalJSON is a custom marshaller for ComplianceRecordNoProductionTraffic struct 293 func (c ComplianceRecordNoProductionTraffic) MarshalJSON() ([]byte, error) { 294 type ComplianceRecord ComplianceRecordNoProductionTraffic 295 v := struct { 296 ComplianceRecord 297 NoncomplianceReason string `json:"noncomplianceReason"` 298 }{ 299 ComplianceRecord(c), 300 c.noncomplianceReasonType(), 301 } 302 return json.Marshal(v) 303 } 304 305 func (c *ComplianceRecordEmergency) noncomplianceReasonType() string { 306 return NoncomplianceReasonEmergency 307 } 308 309 // MarshalJSON is a custom marshaller for ComplianceRecordEmergency struct 310 func (c ComplianceRecordEmergency) MarshalJSON() ([]byte, error) { 311 type ComplianceRecord ComplianceRecordEmergency 312 v := struct { 313 ComplianceRecord 314 NoncomplianceReason string `json:"noncomplianceReason"` 315 }{ 316 ComplianceRecord(c), 317 c.noncomplianceReasonType(), 318 } 319 return json.Marshal(v) 320 } 321 322 // Validate validates DeactivateIncludeRequest 323 func (i DeactivateIncludeRequest) Validate() error { 324 return edgegriderr.ParseValidationErrors(validation.Errors{ 325 "IncludeID": validation.Validate(i.IncludeID, validation.Required), 326 "Version": validation.Validate(i.Version, validation.Required), 327 "Network": validation.Validate(i.Network, validation.Required), 328 "NotifyEmails": validation.Validate(i.NotifyEmails, validation.Required), 329 }) 330 } 331 332 // Validate validates GetIncludeActivationRequest 333 func (i GetIncludeActivationRequest) Validate() error { 334 return edgegriderr.ParseValidationErrors(validation.Errors{ 335 "IncludeID": validation.Validate(i.IncludeID, validation.Required), 336 "ActivationID": validation.Validate(i.ActivationID, validation.Required), 337 }) 338 } 339 340 // Validate validates CancelIncludeActivationRequest 341 func (i CancelIncludeActivationRequest) Validate() error { 342 return edgegriderr.ParseValidationErrors(validation.Errors{ 343 "ContractID": validation.Validate(i.ContractID, validation.Required), 344 "GroupID": validation.Validate(i.GroupID, validation.Required), 345 "IncludeID": validation.Validate(i.IncludeID, validation.Required), 346 "ActivationID": validation.Validate(i.ActivationID, validation.Required), 347 }) 348 } 349 350 // Validate validates ListIncludeActivationsRequest 351 func (i ListIncludeActivationsRequest) Validate() error { 352 return edgegriderr.ParseValidationErrors(validation.Errors{ 353 "IncludeID": validation.Validate(i.IncludeID, validation.Required), 354 "ContractID": validation.Validate(i.ContractID, validation.Required), 355 "GroupID": validation.Validate(i.GroupID, validation.Required), 356 }) 357 } 358 359 var ( 360 // ErrActivateInclude is returned in case an error occurs on ActivateInclude operation 361 ErrActivateInclude = errors.New("activate include") 362 // ErrDeactivateInclude is returned in case an error occurs on DeactivateInclude operation 363 ErrDeactivateInclude = errors.New("deactivate include") 364 // ErrCancelIncludeActivation is returned in case an error occurs on CancelIncludeActivation operation 365 ErrCancelIncludeActivation = errors.New("cancel include activation") 366 // ErrGetIncludeActivation is returned in case an error occurs on GetIncludeActivation operation 367 ErrGetIncludeActivation = errors.New("get include activation") 368 // ErrListIncludeActivations is returned in case an error occurs on ListIncludeActivations operation 369 ErrListIncludeActivations = errors.New("list include activations") 370 ) 371 372 func (p *papi) ActivateInclude(ctx context.Context, params ActivateIncludeRequest) (*ActivationIncludeResponse, error) { 373 logger := p.Log(ctx) 374 logger.Debug("ActivateInclude") 375 376 if err := params.Validate(); err != nil { 377 return nil, fmt.Errorf("%s: %w: %s", ErrActivateInclude, ErrStructValidation, err) 378 } 379 380 if params.IgnoreHTTPErrors == nil { 381 params.IgnoreHTTPErrors = tools.BoolPtr(true) 382 } 383 384 requestBody := struct { 385 ActivateIncludeRequest 386 ActivationType ActivationType `json:"activationType"` 387 }{ 388 params, 389 ActivationTypeActivate, 390 } 391 392 uri := fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID) 393 394 req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil) 395 if err != nil { 396 return nil, fmt.Errorf("%w: failed to create request: %s", ErrActivateInclude, err) 397 } 398 399 var result ActivationIncludeResponse 400 resp, err := p.Exec(req, &result, requestBody) 401 if err != nil { 402 return nil, fmt.Errorf("%w: request failed: %s", ErrActivateInclude, err) 403 } 404 405 if resp.StatusCode != http.StatusCreated { 406 return nil, fmt.Errorf("%s: %w", ErrActivateInclude, p.Error(resp)) 407 } 408 409 id, err := ResponseLinkParse(result.ActivationLink) 410 if err != nil { 411 return nil, fmt.Errorf("%s: %w: %s", ErrActivateInclude, ErrInvalidResponseLink, err) 412 } 413 result.ActivationID = id 414 415 return &result, nil 416 } 417 418 func (p *papi) DeactivateInclude(ctx context.Context, params DeactivateIncludeRequest) (*DeactivationIncludeResponse, error) { 419 logger := p.Log(ctx) 420 logger.Debug("DeactivateInclude") 421 422 if err := params.Validate(); err != nil { 423 return nil, fmt.Errorf("%s: %w: %s", ErrDeactivateInclude, ErrStructValidation, err) 424 } 425 426 if params.IgnoreHTTPErrors == nil { 427 params.IgnoreHTTPErrors = tools.BoolPtr(true) 428 } 429 430 requestBody := struct { 431 DeactivateIncludeRequest 432 ActivationType ActivationType `json:"activationType"` 433 }{ 434 params, 435 ActivationTypeDeactivate, 436 } 437 438 uri := fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID) 439 440 req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil) 441 if err != nil { 442 return nil, fmt.Errorf("%w: failed to create request: %s", ErrDeactivateInclude, err) 443 } 444 445 var result DeactivationIncludeResponse 446 resp, err := p.Exec(req, &result, requestBody) 447 if err != nil { 448 return nil, fmt.Errorf("%w: request failed: %s", ErrDeactivateInclude, err) 449 } 450 451 if resp.StatusCode != http.StatusCreated { 452 return nil, fmt.Errorf("%s: %w", ErrDeactivateInclude, p.Error(resp)) 453 } 454 455 id, err := ResponseLinkParse(result.ActivationLink) 456 if err != nil { 457 return nil, fmt.Errorf("%s: %w: %s", ErrDeactivateInclude, ErrInvalidResponseLink, err) 458 } 459 result.ActivationID = id 460 461 return &result, nil 462 } 463 464 func (p *papi) CancelIncludeActivation(ctx context.Context, params CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error) { 465 logger := p.Log(ctx) 466 logger.Debug("CancelIncludeActivation") 467 468 if err := params.Validate(); err != nil { 469 return nil, fmt.Errorf("%s: %w: %s", ErrCancelIncludeActivation, ErrStructValidation, err) 470 } 471 472 uri, err := url.Parse(fmt.Sprintf("/papi/v1/includes/%s/activations/%s", params.IncludeID, params.ActivationID)) 473 if err != nil { 474 return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCancelIncludeActivation, err) 475 } 476 477 q := uri.Query() 478 q.Add("contractId", params.ContractID) 479 q.Add("groupId", params.GroupID) 480 uri.RawQuery = q.Encode() 481 482 req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil) 483 if err != nil { 484 return nil, fmt.Errorf("%w: failed to create request: %s", ErrCancelIncludeActivation, err) 485 } 486 487 var result CancelIncludeActivationResponse 488 resp, err := p.Exec(req, &result) 489 if err != nil { 490 return nil, fmt.Errorf("%w: request failed: %s", ErrCancelIncludeActivation, err) 491 } 492 493 if resp.StatusCode != http.StatusOK { 494 return nil, fmt.Errorf("%s: %w", ErrCancelIncludeActivation, p.Error(resp)) 495 } 496 497 return &result, nil 498 } 499 500 func (p *papi) GetIncludeActivation(ctx context.Context, params GetIncludeActivationRequest) (*GetIncludeActivationResponse, error) { 501 logger := p.Log(ctx) 502 logger.Debug("GetIncludeActivation") 503 504 if err := params.Validate(); err != nil { 505 return nil, fmt.Errorf("%s: %w: %s", ErrGetIncludeActivation, ErrStructValidation, err) 506 } 507 508 uri := fmt.Sprintf("/papi/v1/includes/%s/activations/%s", params.IncludeID, params.ActivationID) 509 510 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) 511 if err != nil { 512 return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetIncludeActivation, err) 513 } 514 515 var result GetIncludeActivationResponse 516 resp, err := p.Exec(req, &result) 517 if err != nil { 518 return nil, fmt.Errorf("%w: request failed: %s", ErrGetIncludeActivation, err) 519 } 520 521 if resp.StatusCode != http.StatusOK { 522 return nil, fmt.Errorf("%s: %w", ErrGetIncludeActivation, p.Error(resp)) 523 } 524 525 if len(result.Activations.Items) == 0 { 526 return nil, fmt.Errorf("%s: %w: ActivationID: %s", ErrGetIncludeActivation, ErrNotFound, params.ActivationID) 527 } 528 result.Activation = result.Activations.Items[0] 529 530 return &result, nil 531 } 532 533 func (p *papi) ListIncludeActivations(ctx context.Context, params ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error) { 534 logger := p.Log(ctx) 535 logger.Debug("ListIncludeActivations") 536 537 if err := params.Validate(); err != nil { 538 return nil, fmt.Errorf("%s: %w: %s", ErrListIncludeActivations, ErrStructValidation, err) 539 } 540 541 uri, err := url.Parse(fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID)) 542 if err != nil { 543 return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListIncludeActivations, err) 544 } 545 546 q := uri.Query() 547 q.Add("contractId", params.ContractID) 548 q.Add("groupId", params.GroupID) 549 uri.RawQuery = q.Encode() 550 551 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) 552 if err != nil { 553 return nil, fmt.Errorf("%w: failed to create request: %s", ErrListIncludeActivations, err) 554 } 555 556 var result ListIncludeActivationsResponse 557 resp, err := p.Exec(req, &result) 558 if err != nil { 559 return nil, fmt.Errorf("%w: request failed: %s", ErrListIncludeActivations, err) 560 } 561 562 if resp.StatusCode != http.StatusOK { 563 return nil, fmt.Errorf("%s: %w", ErrListIncludeActivations, p.Error(resp)) 564 } 565 566 return &result, nil 567 }