github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.1.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://techdocs.akamai.com/cps/reference/get-enrollment-change
    19  		GetChangeStatus(context.Context, GetChangeStatusRequest) (*Change, error)
    20  
    21  		// CancelChange cancels a pending change
    22  		//
    23  		// See: https://techdocs.akamai.com/cps/reference/delete-enrollment-change
    24  		CancelChange(context.Context, CancelChangeRequest) (*CancelChangeResponse, error)
    25  
    26  		// UpdateChange updates a pending change
    27  		// Deprecated: this function will be removed in a future release. Use one of:
    28  		// AcknowledgeChangeManagement(), AcknowledgePostVerificationWarnings(),
    29  		// AcknowledgePreVerificationWarnings(), UploadThirdPartyCertAndTrustChain()
    30  		// or AcknowledgeDVChallenges()
    31  		//
    32  		// See: https://techdocs.akamai.com/cps/reference/post-change-allowed-input-param
    33  		UpdateChange(context.Context, UpdateChangeRequest) (*UpdateChangeResponse, error)
    34  	}
    35  
    36  	// Change contains change status information
    37  	Change struct {
    38  		AllowedInput []AllowedInput `json:"allowedInput"`
    39  		StatusInfo   *StatusInfo    `json:"statusInfo"`
    40  	}
    41  
    42  	// AllowedInput contains the resource locations (path) of data inputs allowed by this Change
    43  	AllowedInput struct {
    44  		Info              string `json:"info"`
    45  		RequiredToProceed bool   `json:"requiredToProceed"`
    46  		Type              string `json:"type"`
    47  		Update            string `json:"update"`
    48  	}
    49  
    50  	// StatusInfo contains the status for this Change at this time
    51  	StatusInfo struct {
    52  		DeploymentSchedule *DeploymentSchedule `json:"deploymentSchedule"`
    53  		Description        string              `json:"description"`
    54  		Error              *StatusInfoError    `json:"error,omitempty"`
    55  		State              string              `json:"state"`
    56  		Status             string              `json:"status"`
    57  	}
    58  
    59  	// StatusInfoError contains error information for this Change
    60  	StatusInfoError struct {
    61  		Code        string `json:"code"`
    62  		Description string `json:"description"`
    63  		Timestamp   string `json:"timestamp"`
    64  	}
    65  
    66  	// Certificate is a digital certificate object
    67  	Certificate struct {
    68  		Certificate string `json:"certificate"`
    69  		TrustChain  string `json:"trustChain,omitempty"`
    70  	}
    71  
    72  	// GetChangeStatusRequest contains params required to perform GetChangeStatus
    73  	GetChangeStatusRequest struct {
    74  		EnrollmentID int
    75  		ChangeID     int
    76  	}
    77  
    78  	// GetChangeRequest contains params required to fetch a specific change (e.g. DV challenges)
    79  	// It is the same for all GET change requests
    80  	GetChangeRequest struct {
    81  		EnrollmentID int
    82  		ChangeID     int
    83  	}
    84  
    85  	// CancelChangeRequest contains params required to send CancelChange request
    86  	CancelChangeRequest struct {
    87  		EnrollmentID int
    88  		ChangeID     int
    89  	}
    90  
    91  	// CancelChangeResponse is a response object returned from CancelChange request
    92  	CancelChangeResponse struct {
    93  		Change string `json:"change"`
    94  	}
    95  
    96  	// UpdateChangeRequest contains params and body required to send UpdateChange request
    97  	UpdateChangeRequest struct {
    98  		Certificate
    99  		EnrollmentID          int
   100  		ChangeID              int
   101  		AllowedInputTypeParam AllowedInputType
   102  	}
   103  
   104  	// UpdateChangeResponse is a response object returned from UpdateChange request
   105  	UpdateChangeResponse struct {
   106  		Change string `json:"change"`
   107  	}
   108  
   109  	// AcknowledgementRequest contains params and body required to send acknowledgement. It is the same for all acknowledgement types (dv, pre-verification-warnings etc.)
   110  	AcknowledgementRequest struct {
   111  		Acknowledgement
   112  		EnrollmentID int
   113  		ChangeID     int
   114  	}
   115  
   116  	// Acknowledgement is a request body of acknowledgement request
   117  	Acknowledgement struct {
   118  		Acknowledgement string `json:"acknowledgement"`
   119  	}
   120  
   121  	// AllowedInputType represents allowedInputTypeParam used for fetching and updating changes
   122  	AllowedInputType string
   123  )
   124  
   125  const (
   126  	// AllowedInputTypeChangeManagementACK parameter value
   127  	AllowedInputTypeChangeManagementACK AllowedInputType = "change-management-ack"
   128  	// AllowedInputTypeLetsEncryptChallengesCompleted parameter value
   129  	AllowedInputTypeLetsEncryptChallengesCompleted AllowedInputType = "lets-encrypt-challenges-completed"
   130  	// AllowedInputTypePostVerificationWarningsACK parameter value
   131  	AllowedInputTypePostVerificationWarningsACK AllowedInputType = "post-verification-warnings-ack"
   132  	// AllowedInputTypePreVerificationWarningsACK parameter value
   133  	AllowedInputTypePreVerificationWarningsACK AllowedInputType = "pre-verification-warnings-ack"
   134  	// AllowedInputTypeThirdPartyCertAndTrustChain parameter value
   135  	AllowedInputTypeThirdPartyCertAndTrustChain AllowedInputType = "third-party-cert-and-trust-chain"
   136  )
   137  
   138  const (
   139  	// AcknowledgementAcknowledge parameter value
   140  	AcknowledgementAcknowledge = "acknowledge"
   141  	// AcknowledgementDeny parameter value
   142  	AcknowledgementDeny = "deny"
   143  )
   144  
   145  // AllowedInputContentTypeHeader maps content type headers to specific allowed input type params
   146  var AllowedInputContentTypeHeader = map[AllowedInputType]string{
   147  	AllowedInputTypeChangeManagementACK:            "application/vnd.akamai.cps.acknowledgement-with-hash.v1+json",
   148  	AllowedInputTypeLetsEncryptChallengesCompleted: "application/vnd.akamai.cps.acknowledgement.v1+json",
   149  	AllowedInputTypePostVerificationWarningsACK:    "application/vnd.akamai.cps.acknowledgement.v1+json",
   150  	AllowedInputTypePreVerificationWarningsACK:     "application/vnd.akamai.cps.acknowledgement.v1+json",
   151  	AllowedInputTypeThirdPartyCertAndTrustChain:    "application/vnd.akamai.cps.certificate-and-trust-chain.v1+json",
   152  }
   153  
   154  // Validate validates GetChangeRequest
   155  func (c GetChangeRequest) Validate() error {
   156  	return validation.Errors{
   157  		"EnrollmentID": validation.Validate(c.EnrollmentID, validation.Required),
   158  		"ChangeID":     validation.Validate(c.ChangeID, validation.Required),
   159  	}.Filter()
   160  }
   161  
   162  // Validate validates GetChangeStatusRequest
   163  func (c GetChangeStatusRequest) Validate() error {
   164  	return validation.Errors{
   165  		"EnrollmentID": validation.Validate(c.EnrollmentID, validation.Required),
   166  		"ChangeID":     validation.Validate(c.ChangeID, validation.Required),
   167  	}.Filter()
   168  }
   169  
   170  // Validate validates CancelChangeRequest
   171  func (c CancelChangeRequest) Validate() error {
   172  	return validation.Errors{
   173  		"EnrollmentID": validation.Validate(c.EnrollmentID, validation.Required),
   174  		"ChangeID":     validation.Validate(c.ChangeID, validation.Required),
   175  	}.Filter()
   176  }
   177  
   178  // Validate validates UpdateChangeRequest
   179  func (c UpdateChangeRequest) Validate() error {
   180  	return validation.Errors{
   181  		"EnrollmentID": validation.Validate(c.EnrollmentID, validation.Required),
   182  		"ChangeID":     validation.Validate(c.ChangeID, validation.Required),
   183  		"AllowedInputTypeParam": validation.Validate(c.AllowedInputTypeParam, validation.In(
   184  			AllowedInputTypeChangeManagementACK,
   185  			AllowedInputTypeLetsEncryptChallengesCompleted,
   186  			AllowedInputTypePostVerificationWarningsACK,
   187  			AllowedInputTypePreVerificationWarningsACK,
   188  			AllowedInputTypeThirdPartyCertAndTrustChain,
   189  		)),
   190  		"Certificate": validation.Validate(c.Certificate, validation.Required),
   191  	}.Filter()
   192  }
   193  
   194  // Validate validates AcknowledgementRequest
   195  func (a AcknowledgementRequest) Validate() error {
   196  	return validation.Errors{
   197  		"EnrollmentID":    validation.Validate(a.EnrollmentID, validation.Required),
   198  		"ChangeID":        validation.Validate(a.ChangeID, validation.Required),
   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  }