github.com/akamai/AkamaiOPEN-edgegrid-golang/v2@v2.17.0/pkg/cps/changes.go (about) 1 package cps 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 "net/url" 9 10 validation "github.com/go-ozzo/ozzo-validation/v4" 11 ) 12 13 type ( 14 // ChangeOperations is a CPS change API interface 15 ChangeOperations interface { 16 // GetChangeStatus fetches change status for given enrollment and change ID 17 // 18 // See: https://developer.akamai.com/api/core_features/certificate_provisioning_system/v2.html#getasinglechange 19 GetChangeStatus(context.Context, GetChangeStatusRequest) (*Change, error) 20 21 // CancelChange cancels a pending change 22 // 23 // See: https://developer.akamai.com/api/core_features/certificate_provisioning_system/v2.html#deleteasinglechange 24 CancelChange(context.Context, CancelChangeRequest) (*CancelChangeResponse, error) 25 26 // UpdateChange updates a pending change 27 // 28 // See: https://developer.akamai.com/api/core_features/certificate_provisioning_system/v2.html#postallowedinputtypeforupdate 29 UpdateChange(context.Context, UpdateChangeRequest) (*UpdateChangeResponse, error) 30 } 31 32 // Change contains change status information 33 Change struct { 34 AllowedInput []AllowedInput `json:"allowedInput"` 35 StatusInfo *StatusInfo `json:"statusInfo"` 36 } 37 38 // AllowedInput contains the resource locations (path) of data inputs allowed by this Change 39 AllowedInput struct { 40 Info string `json:"info"` 41 RequiredToProceed bool `json:"requiredToProceed"` 42 Type string `json:"type"` 43 Update string `json:"update"` 44 } 45 46 // StatusInfo contains he tstatus for this Change at this time 47 StatusInfo struct { 48 DeploymentSchedule *DeploymentSchedule `json:"deploymentSchedule"` 49 Description string `json:"description"` 50 Error *StatusInfoError `json:"error,omitempty"` 51 State string `json:"state"` 52 Status string `json:"status"` 53 } 54 55 // DeploymentSchedule contains the schedule for when you want this change deploy 56 DeploymentSchedule struct { 57 NotAfter string `json:"notAfter,omitempty"` 58 NotBefore string `json:"notBefore,omitempty"` 59 } 60 61 // StatusInfoError contains error information for this Change 62 StatusInfoError struct { 63 Code string `json:"code"` 64 Description string `json:"description"` 65 Timestamp string `json:"timestamp"` 66 } 67 68 // Certificate is a digital certificate object 69 Certificate struct { 70 Certificate string `json:"certificate"` 71 TrustChain string `json:"trustChain"` 72 } 73 74 // GetChangeStatusRequest contains params required to perform GetChangeStatus 75 GetChangeStatusRequest struct { 76 EnrollmentID int 77 ChangeID int 78 } 79 80 // GetChangeRequest contains params required to fetch a specific change (e.g. DV challenges) 81 // It is the same for all GET change requests 82 GetChangeRequest struct { 83 EnrollmentID int 84 ChangeID int 85 } 86 87 // CancelChangeRequest contains params required to send CancelChange request 88 CancelChangeRequest struct { 89 EnrollmentID int 90 ChangeID int 91 } 92 93 // CancelChangeResponse is a response object returned from CancelChange request 94 CancelChangeResponse struct { 95 Change string `json:"change"` 96 } 97 98 // UpdateChangeRequest contains params and body required to send UpdateChange request 99 UpdateChangeRequest struct { 100 Certificate 101 EnrollmentID int 102 ChangeID int 103 AllowedInputTypeParam AllowedInputType 104 } 105 106 // UpdateChangeResponse is a response object returned from UpdateChange request 107 UpdateChangeResponse struct { 108 Change string `json:"change"` 109 } 110 111 // AcknowledgementRequest contains params and body required to send acknowledgement. It is the same for all acknowledgement types (dv, pre-verification-warnings etc.) 112 AcknowledgementRequest struct { 113 Acknowledgement 114 EnrollmentID int 115 ChangeID int 116 } 117 118 // Acknowledgement is a request body of acknowledgement request 119 Acknowledgement struct { 120 Acknowledgement string `json:"acknowledgement"` 121 } 122 123 // AllowedInputType represents allowedInputTypeParam used for fetching and updating changes 124 AllowedInputType string 125 ) 126 127 const ( 128 // AllowedInputTypeChangeManagementACK parameter value 129 AllowedInputTypeChangeManagementACK AllowedInputType = "change-management-ack" 130 // AllowedInputTypeLetsEncryptChallengesCompleted parameter value 131 AllowedInputTypeLetsEncryptChallengesCompleted AllowedInputType = "lets-encrypt-challenges-completed" 132 // AllowedInputTypePostVerificationWarningsACK parameter value 133 AllowedInputTypePostVerificationWarningsACK AllowedInputType = "post-verification-warnings-ack" 134 // AllowedInputTypePreVerificationWarningsACK parameter value 135 AllowedInputTypePreVerificationWarningsACK AllowedInputType = "pre-verification-warnings-ack" 136 // AllowedInputTypeThirdPartyCertAndTrustChain parameter value 137 AllowedInputTypeThirdPartyCertAndTrustChain AllowedInputType = "third-party-cert-and-trust-chain" 138 ) 139 140 const ( 141 // AcknowledgementAcknowledge parameter value 142 AcknowledgementAcknowledge = "acknowledge" 143 // AcknowledgementDeny parameter value 144 AcknowledgementDeny = "deny" 145 ) 146 147 // AllowedInputContentTypeHeader maps content type headers to specific allowed input type params 148 var AllowedInputContentTypeHeader = map[AllowedInputType]string{ 149 AllowedInputTypeChangeManagementACK: "application/vnd.akamai.cps.acknowledgement-with-hash.v1+json", 150 AllowedInputTypeLetsEncryptChallengesCompleted: "application/vnd.akamai.cps.acknowledgement.v1+json", 151 AllowedInputTypePostVerificationWarningsACK: "application/vnd.akamai.cps.acknowledgement.v1+json", 152 AllowedInputTypePreVerificationWarningsACK: "application/vnd.akamai.cps.acknowledgement.v1+json", 153 AllowedInputTypeThirdPartyCertAndTrustChain: "application/vnd.akamai.cps.certificate-and-trust-chain.v1+json", 154 } 155 156 // Validate validates GetChangeRequest 157 func (c GetChangeRequest) Validate() error { 158 return validation.Errors{ 159 "enrollmentId": validation.Validate(c.EnrollmentID, validation.Required), 160 "changeId": validation.Validate(c.ChangeID, validation.Required), 161 }.Filter() 162 } 163 164 // Validate validates GetChangeStatusRequest 165 func (c GetChangeStatusRequest) Validate() error { 166 return validation.Errors{ 167 "enrollmentId": validation.Validate(c.EnrollmentID, validation.Required), 168 "changeId": validation.Validate(c.ChangeID, validation.Required), 169 }.Filter() 170 } 171 172 // Validate validates CancelChangeRequest 173 func (c CancelChangeRequest) Validate() error { 174 return validation.Errors{ 175 "enrollmentId": validation.Validate(c.EnrollmentID, validation.Required), 176 "changeId": validation.Validate(c.ChangeID, validation.Required), 177 }.Filter() 178 } 179 180 // Validate validates UpdateChangeRequest 181 func (c UpdateChangeRequest) Validate() error { 182 return validation.Errors{ 183 "enrollmentId": validation.Validate(c.EnrollmentID, validation.Required), 184 "changeId": validation.Validate(c.ChangeID, validation.Required), 185 "allowedInputTypeParam": validation.Validate(c.AllowedInputTypeParam, validation.In( 186 AllowedInputTypeChangeManagementACK, 187 AllowedInputTypeLetsEncryptChallengesCompleted, 188 AllowedInputTypePostVerificationWarningsACK, 189 AllowedInputTypePreVerificationWarningsACK, 190 AllowedInputTypeThirdPartyCertAndTrustChain, 191 )), 192 "certificate": validation.Validate(c.Certificate, validation.Required), 193 }.Filter() 194 } 195 196 // Validate validates AcknowledgementRequest 197 func (a AcknowledgementRequest) Validate() error { 198 return validation.Errors{ 199 "acknowledgement": validation.Validate(a.Acknowledgement), 200 }.Filter() 201 } 202 203 // Validate validates Acknowledgement 204 func (a Acknowledgement) Validate() error { 205 return validation.Errors{ 206 "acknowledgement": validation.Validate(a.Acknowledgement, validation.Required, validation.In(AcknowledgementAcknowledge, AcknowledgementDeny)), 207 }.Filter() 208 } 209 210 // Validate validates Certificate 211 func (c Certificate) Validate() error { 212 return validation.Errors{ 213 "certificate": validation.Validate(c.Certificate, validation.Required), 214 }.Filter() 215 } 216 217 var ( 218 // ErrGetChangeStatus is returned when GetChangeStatus fails 219 ErrGetChangeStatus = errors.New("fetching change") 220 // ErrCancelChange is returned when CancelChange fails 221 ErrCancelChange = errors.New("canceling change") 222 // ErrUpdateChange is returned when UpdateChange fails 223 ErrUpdateChange = errors.New("updating change") 224 ) 225 226 func (c *cps) GetChangeStatus(ctx context.Context, params GetChangeStatusRequest) (*Change, error) { 227 if err := params.Validate(); err != nil { 228 return nil, fmt.Errorf("%s: %w: %s", ErrGetChangeStatus, ErrStructValidation, err) 229 } 230 231 var rval Change 232 233 logger := c.Log(ctx) 234 logger.Debug("GetChangeStatus") 235 236 uri, err := url.Parse(fmt.Sprintf( 237 "/cps/v2/enrollments/%d/changes/%d", 238 params.EnrollmentID, 239 params.ChangeID), 240 ) 241 if err != nil { 242 return nil, fmt.Errorf("%w: failed to parse url: %s", ErrGetChangeStatus, err) 243 } 244 245 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) 246 if err != nil { 247 return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetChangeStatus, err) 248 } 249 req.Header.Set("Accept", "application/vnd.akamai.cps.change.v2+json") 250 251 resp, err := c.Exec(req, &rval) 252 if err != nil { 253 return nil, fmt.Errorf("%w: request failed: %s", ErrGetChangeStatus, err) 254 } 255 256 if resp.StatusCode != http.StatusOK { 257 return nil, fmt.Errorf("%s: %w", ErrGetChangeStatus, c.Error(resp)) 258 } 259 260 return &rval, nil 261 } 262 263 func (c *cps) CancelChange(ctx context.Context, params CancelChangeRequest) (*CancelChangeResponse, error) { 264 if err := params.Validate(); err != nil { 265 return nil, fmt.Errorf("%s: %w: %s", ErrCancelChange, ErrStructValidation, err) 266 } 267 268 var rval CancelChangeResponse 269 270 logger := c.Log(ctx) 271 logger.Debug("CancelChange") 272 273 uri, err := url.Parse(fmt.Sprintf( 274 "/cps/v2/enrollments/%d/changes/%d", 275 params.EnrollmentID, 276 params.ChangeID), 277 ) 278 if err != nil { 279 return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCancelChange, err) 280 } 281 282 req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil) 283 if err != nil { 284 return nil, fmt.Errorf("%w: failed to create request: %s", ErrCancelChange, err) 285 } 286 req.Header.Set("Accept", "application/vnd.akamai.cps.change-id.v1+json") 287 288 resp, err := c.Exec(req, &rval) 289 if err != nil { 290 return nil, fmt.Errorf("%w: request failed: %s", ErrCancelChange, err) 291 } 292 293 if resp.StatusCode != http.StatusOK { 294 return nil, fmt.Errorf("%s: %w", ErrCancelChange, c.Error(resp)) 295 } 296 297 return &rval, nil 298 } 299 300 func (c *cps) UpdateChange(ctx context.Context, params UpdateChangeRequest) (*UpdateChangeResponse, error) { 301 if err := params.Validate(); err != nil { 302 return nil, fmt.Errorf("%s: %w: %s", ErrUpdateChange, ErrStructValidation, err) 303 } 304 305 var rval UpdateChangeResponse 306 307 logger := c.Log(ctx) 308 logger.Debug("UpdateChangeLetsEncryptChallenges") 309 310 uri, err := url.Parse(fmt.Sprintf( 311 "/cps/v2/enrollments/%d/changes/%d/input/update/%s", 312 params.EnrollmentID, 313 params.ChangeID, 314 params.AllowedInputTypeParam), 315 ) 316 if err != nil { 317 return nil, fmt.Errorf("%w: failed to parse url: %s", ErrUpdateChange, err) 318 } 319 320 req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil) 321 if err != nil { 322 return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateChange, err) 323 } 324 req.Header.Set("Accept", "application/vnd.akamai.cps.change-id.v1+json") 325 req.Header.Set("Content-Type", AllowedInputContentTypeHeader[params.AllowedInputTypeParam]) 326 327 resp, err := c.Exec(req, &rval) 328 if err != nil { 329 return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateChange, err) 330 } 331 332 if resp.StatusCode != http.StatusOK { 333 return nil, fmt.Errorf("%s: %w", ErrUpdateChange, c.Error(resp)) 334 } 335 336 return &rval, nil 337 }