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  }