From fe0beb8a79bef08bb89c222534b8b8a8285fe77f Mon Sep 17 00:00:00 2001 From: Roberto Lopez Lopez Date: Mon, 17 Jan 2022 07:54:55 +0000 Subject: [PATCH 01/17] DXE-333 Feature/security v2 january new Merge in DEVEXP/akamaiopen-edgegrid-golang from feature/security-v2-january-new to v2 --- CHANGELOG.md | 11 + .../advanced_settings_evasive_path_match.go | 190 ++++ ...vanced_settings_evasive_path_match_test.go | 243 +++++ pkg/appsec/appsec.go | 1 + pkg/appsec/export_configuration.go | 12 +- pkg/networklists/network_list.go | 34 +- pkg/networklists/network_list_test.go | 77 ++ .../TestNetworkList/NetworkLists_GEO.json | 996 ++++++++++++++++++ 8 files changed, 1548 insertions(+), 16 deletions(-) create mode 100644 pkg/appsec/advanced_settings_evasive_path_match.go create mode 100644 pkg/appsec/advanced_settings_evasive_path_match_test.go create mode 100644 pkg/networklists/testdata/TestNetworkList/NetworkLists_GEO.json diff --git a/CHANGELOG.md b/CHANGELOG.md index b693d326..c60ab1a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # EDGEGRID GOLANG RELEASE NOTES +## 2.9.0 (Jan. 11, 2022) + +#### FEATURES/ENHANCEMENTS: + +* APPSEC + * Add support for Evasive Path Match feature + * Deprecate individual policy protection interface methods + +* NETWORK LISTS + * Include ContractID and GroupID in GetNetworkListResponse + ## 2.8.1 (Nov. 30, 2021) #### FEATURES/ENHANCEMENTS: diff --git a/pkg/appsec/advanced_settings_evasive_path_match.go b/pkg/appsec/advanced_settings_evasive_path_match.go new file mode 100644 index 00000000..23935bb4 --- /dev/null +++ b/pkg/appsec/advanced_settings_evasive_path_match.go @@ -0,0 +1,190 @@ +package appsec + +import ( + "context" + "fmt" + "net/http" + + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // The AdvancedSettingsEvasivePathMatch supports retrieving or modifying the Evasive Path Match setting. + // + // https://developer.akamai.com/api/cloud_security/application_security/v1.html + AdvancedSettingsEvasivePathMatch interface { + // GetAdvancedSettingsEvasivePathMatch retrieves the Evasive Path Match setting + GetAdvancedSettingsEvasivePathMatch(ctx context.Context, params GetAdvancedSettingsEvasivePathMatchRequest) (*GetAdvancedSettingsEvasivePathMatchResponse, error) + // UpdateAdvancedSettingsEvasivePathMatch modifies the Evasive Path Match setting + UpdateAdvancedSettingsEvasivePathMatch(ctx context.Context, params UpdateAdvancedSettingsEvasivePathMatchRequest) (*UpdateAdvancedSettingsEvasivePathMatchResponse, error) + // RemoveAdvancedSettingsEvasivePathMatch removes the Evasive Path Match setting + RemoveAdvancedSettingsEvasivePathMatch(ctx context.Context, params RemoveAdvancedSettingsEvasivePathMatchRequest) (*RemoveAdvancedSettingsEvasivePathMatchResponse, error) + } + + // GetAdvancedSettingsEvasivePathMatchRequest is used to retrieve the EvasivePathMatch setting + GetAdvancedSettingsEvasivePathMatchRequest struct { + ConfigID int `json:"-"` + Version int `json:"-"` + PolicyID string `json:"-"` + } + + // GetAdvancedSettingsEvasivePathMatchResponse returns the EvasivePathMatch setting + GetAdvancedSettingsEvasivePathMatchResponse struct { + EnablePathMatch bool `json:"enablePathMatch"` + } + + // UpdateAdvancedSettingsEvasivePathMatchRequest is used to update the EvasivePathMatch setting + UpdateAdvancedSettingsEvasivePathMatchRequest struct { + ConfigID int `json:"-"` + Version int `json:"-"` + PolicyID string `json:"-"` + EnablePathMatch bool `json:"enablePathMatch"` + } + + // UpdateAdvancedSettingsEvasivePathMatchResponse returns the result of updating the EvasivePathMatch setting + UpdateAdvancedSettingsEvasivePathMatchResponse struct { + EnablePathMatch bool `json:"enablePathMatch"` + } + + // RemoveAdvancedSettingsEvasivePathMatchRequest is used to clear the EvasivePathMatch setting + RemoveAdvancedSettingsEvasivePathMatchRequest struct { + ConfigID int `json:"-"` + Version int `json:"-"` + PolicyID string `json:"-"` + EnablePathMatch bool `json:"enablePathMatch"` + } + + // RemoveAdvancedSettingsEvasivePathMatchResponse returns the result of clearing the EvasivePathMatch setting + RemoveAdvancedSettingsEvasivePathMatchResponse struct { + ConfigID int `json:"-"` + Version int `json:"-"` + PolicyID string `json:"-"` + EnablePathMatch bool `json:"enablePathMatch"` + } +) + +// Validate validates GetAdvancedSettingssEvasivePathMatchRequest +func (v GetAdvancedSettingsEvasivePathMatchRequest) Validate() error { + return validation.Errors{ + "ConfigID": validation.Validate(v.ConfigID, validation.Required), + "Version": validation.Validate(v.Version, validation.Required), + }.Filter() +} + +// Validate validates UpdateAdvancedSettingsEvasivePathMatchRequest +func (v UpdateAdvancedSettingsEvasivePathMatchRequest) Validate() error { + return validation.Errors{ + "ConfigID": validation.Validate(v.ConfigID, validation.Required), + "Version": validation.Validate(v.Version, validation.Required), + }.Filter() +} + +// Validate validates UpdateAdvancedSettingsEvasivePathMatchRequest +func (v RemoveAdvancedSettingsEvasivePathMatchRequest) Validate() error { + return validation.Errors{ + "ConfigID": validation.Validate(v.ConfigID, validation.Required), + "Version": validation.Validate(v.Version, validation.Required), + }.Filter() +} + +func (p *appsec) GetAdvancedSettingsEvasivePathMatch(ctx context.Context, params GetAdvancedSettingsEvasivePathMatchRequest) (*GetAdvancedSettingsEvasivePathMatchResponse, error) { + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error()) + } + + logger := p.Log(ctx) + logger.Debug("GetAdvancedSettingsLoggings") + + var rval GetAdvancedSettingsEvasivePathMatchResponse + var uri string + + if params.PolicyID != "" { + uri = fmt.Sprintf( + "/appsec/v1/configs/%d/versions/%d/security-policies/%s/advanced-settings/evasive-path-match", + params.ConfigID, + params.Version, + params.PolicyID) + } else { + uri = fmt.Sprintf( + "/appsec/v1/configs/%d/versions/%d/advanced-settings/evasive-path-match", + params.ConfigID, + params.Version) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, fmt.Errorf("failed to create getadvancedsettingsloggings request: %w", err) + } + + resp, err := p.Exec(req, &rval) + if err != nil { + return nil, fmt.Errorf("getadvancedsettingsloggings request failed: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, p.Error(resp) + } + + return &rval, nil +} + +func (p *appsec) UpdateAdvancedSettingsEvasivePathMatch(ctx context.Context, params UpdateAdvancedSettingsEvasivePathMatchRequest) (*UpdateAdvancedSettingsEvasivePathMatchResponse, error) { + if err := params.Validate(); err != nil { + return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error()) + } + + logger := p.Log(ctx) + logger.Debug("UpdateAdvancedSettingsLogging") + + var putURL string + if params.PolicyID != "" { + putURL = fmt.Sprintf( + "/appsec/v1/configs/%d/versions/%d/security-policies/%s/advanced-settings/evasive-path-match", + params.ConfigID, + params.Version, + params.PolicyID) + } else { + putURL = fmt.Sprintf( + "/appsec/v1/configs/%d/versions/%d/advanced-settings/evasive-path-match", + params.ConfigID, + params.Version) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create create AdvancedSettingsLoggingrequest: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + var rval UpdateAdvancedSettingsEvasivePathMatchResponse + resp, err := p.Exec(req, &rval, params) + if err != nil { + return nil, fmt.Errorf("create AdvancedSettingsLogging request failed: %w", err) + } + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + return nil, p.Error(resp) + } + + return &rval, nil +} + +func (p *appsec) RemoveAdvancedSettingsEvasivePathMatch(ctx context.Context, params RemoveAdvancedSettingsEvasivePathMatchRequest) (*RemoveAdvancedSettingsEvasivePathMatchResponse, error) { + request := UpdateAdvancedSettingsEvasivePathMatchRequest{ + ConfigID: params.ConfigID, + Version: params.Version, + PolicyID: params.PolicyID, + EnablePathMatch: false, + } + _, err := p.UpdateAdvancedSettingsEvasivePathMatch(ctx, request) + if err != nil { + return nil, fmt.Errorf("UpdateAdvancedSettingsEvasivePathMatch request failed: %w", err) + } + response := RemoveAdvancedSettingsEvasivePathMatchResponse{ + ConfigID: params.ConfigID, + Version: params.Version, + PolicyID: params.PolicyID, + EnablePathMatch: false, + } + return &response, nil +} diff --git a/pkg/appsec/advanced_settings_evasive_path_match_test.go b/pkg/appsec/advanced_settings_evasive_path_match_test.go new file mode 100644 index 00000000..a051059e --- /dev/null +++ b/pkg/appsec/advanced_settings_evasive_path_match_test.go @@ -0,0 +1,243 @@ +package appsec + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/session" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestApsec_ListAdvancedSettingsEvasivePathMatch(t *testing.T) { + + result := GetAdvancedSettingsEvasivePathMatchResponse{} + + respData := compactJSON(loadFixtureBytes("testdata/TestAdvancedSettingsEvasivePathMatch/AdvancedSettingsEvasivePathMatch.json")) + json.Unmarshal([]byte(respData), &result) + + tests := map[string]struct { + params GetAdvancedSettingsEvasivePathMatchRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetAdvancedSettingsEvasivePathMatchResponse + withError error + headers http.Header + }{ + "200 OK": { + params: GetAdvancedSettingsEvasivePathMatchRequest{ + ConfigID: 43253, + Version: 15, + }, + headers: http.Header{ + "Content-Type": []string{"application/json"}, + }, + responseStatus: http.StatusOK, + responseBody: string(respData), + expectedPath: "/appsec/v1/configs/43253/versions/15/advanced-settings/evasive-path-match", + expectedResponse: &result, + }, + "500 internal server error": { + params: GetAdvancedSettingsEvasivePathMatchRequest{ + ConfigID: 43253, + Version: 15, + }, + headers: http.Header{}, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error fetching AdvancedSettingsEvasivePathMatch", + "status": 500 +}`, + expectedPath: "/appsec/v1/configs/43253/versions/15/advanced-settings/evasive-path-match", + withError: &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error fetching AdvancedSettingsEvasivePathMatch", + StatusCode: http.StatusInternalServerError, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(test.responseStatus) + _, err := w.Write([]byte(test.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetAdvancedSettingsEvasivePathMatch( + session.ContextWithOptions( + context.Background(), + session.WithContextHeaders(test.headers), + ), + test.params) + if test.withError != nil { + assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedResponse, result) + }) + } +} + +// Test AdvancedSettingsEvasivePathMatch +func TestAppSec_GetAdvancedSettingsEvasivePathmatch(t *testing.T) { + + result := GetAdvancedSettingsEvasivePathMatchResponse{} + + respData := compactJSON(loadFixtureBytes("testdata/TestAdvancedSettingsEvasivePathMatch/AdvancedSettingsEvasivePathMatch.json")) + json.Unmarshal([]byte(respData), &result) + + tests := map[string]struct { + params GetAdvancedSettingsEvasivePathMatchRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetAdvancedSettingsEvasivePathMatchResponse + withError error + }{ + "200 OK": { + params: GetAdvancedSettingsEvasivePathMatchRequest{ + ConfigID: 43253, + Version: 15, + }, + responseStatus: http.StatusOK, + responseBody: respData, + expectedPath: "/appsec/v1/configs/43253/versions/15/advanced-settings/evasive-path-match", + expectedResponse: &result, + }, + "500 internal server error": { + params: GetAdvancedSettingsEvasivePathMatchRequest{ + ConfigID: 43253, + Version: 15, + }, + responseStatus: http.StatusInternalServerError, + responseBody: (` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error fetching AdvancedSettingsEvasivePathMatch" +}`), + expectedPath: "/appsec/v1/configs/43253/versions/15/advanced-settings/evasive-path-match", + withError: &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error fetching AdvancedSettingsEvasivePathMatch", + StatusCode: http.StatusInternalServerError, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(test.responseStatus) + _, err := w.Write([]byte(test.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetAdvancedSettingsEvasivePathMatch(context.Background(), test.params) + if test.withError != nil { + assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedResponse, result) + }) + } +} + +// Test Update AdvancedSettingsEvasivePathMatch. +func TestAppSec_UpdateAdvancedSettingsEvasivePathMatch(t *testing.T) { + result := UpdateAdvancedSettingsEvasivePathMatchResponse{} + + respData := compactJSON(loadFixtureBytes("testdata/TestAdvancedSettingsEvasivePathMatch/AdvancedSettingsEvasivePathMatch.json")) + json.Unmarshal([]byte(respData), &result) + + req := UpdateAdvancedSettingsEvasivePathMatchRequest{} + + reqData := compactJSON(loadFixtureBytes("testdata/TestAdvancedSettingsEvasivePathMatch/AdvancedSettingsEvasivePathMatch.json")) + json.Unmarshal([]byte(reqData), &req) + + tests := map[string]struct { + params UpdateAdvancedSettingsEvasivePathMatchRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *UpdateAdvancedSettingsEvasivePathMatchResponse + withError error + headers http.Header + }{ + "200 Success": { + params: UpdateAdvancedSettingsEvasivePathMatchRequest{ + ConfigID: 43253, + Version: 15, + }, + headers: http.Header{ + "Content-Type": []string{"application/json;charset=UTF-8"}, + }, + responseStatus: http.StatusCreated, + responseBody: respData, + expectedResponse: &result, + expectedPath: "/appsec/v1/configs/43253/versions/15/advanced-settings/evasive-path-match", + }, + "500 internal server error": { + params: UpdateAdvancedSettingsEvasivePathMatchRequest{ + ConfigID: 43253, + Version: 15, + }, + responseStatus: http.StatusInternalServerError, + responseBody: (` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error creating AdvancedSettingsEvasivePathMatch" +}`), + expectedPath: "/appsec/v1/configs/43253/versions/15/advanced-settings/evasive-path-match", + withError: &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error creating AdvancedSettingsEvasivePathMatch", + StatusCode: http.StatusInternalServerError, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method) + w.WriteHeader(test.responseStatus) + if len(test.responseBody) > 0 { + _, err := w.Write([]byte(test.responseBody)) + assert.NoError(t, err) + } + })) + client := mockAPIClient(t, mockServer) + result, err := client.UpdateAdvancedSettingsEvasivePathMatch( + session.ContextWithOptions( + context.Background(), + session.WithContextHeaders(test.headers)), test.params) + if test.withError != nil { + assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedResponse, result) + }) + } +} diff --git a/pkg/appsec/appsec.go b/pkg/appsec/appsec.go index 8c58791b..dedd4987 100644 --- a/pkg/appsec/appsec.go +++ b/pkg/appsec/appsec.go @@ -16,6 +16,7 @@ type ( // APPSEC is the appsec api interface APPSEC interface { Activations + AdvancedSettingsEvasivePathMatch AdvancedSettingsLogging AdvancedSettingsPrefetch AdvancedSettingsPragma diff --git a/pkg/appsec/export_configuration.go b/pkg/appsec/export_configuration.go index 70c06c29..1fb529e7 100644 --- a/pkg/appsec/export_configuration.go +++ b/pkg/appsec/export_configuration.go @@ -206,6 +206,7 @@ type ( SlowPost *SlowPostexp `json:"slowPost,omitempty"` LoggingOverrides *LoggingOverridesexp `json:"loggingOverrides,omitempty"` PragmaHeader *GetAdvancedSettingsPragmaResponse `json:"pragmaHeader,omitempty"` + EvasivePathMatch *EvasivePathMatchexp `json:"evasivePathMatch,omitempty"` } `json:"securityPolicies"` Siem *Siemexp `json:"siem,omitempty"` AdvancedOptions *AdvancedOptionsexp `json:"advancedOptions,omitempty"` @@ -403,6 +404,7 @@ type ( SlowPost *SlowPostexp `json:"slowPost,omitempty"` LoggingOverrides *LoggingOverridesexp `json:"loggingOverrides,omitempty"` PragmaHeader *GetAdvancedSettingsPragmaResponse `json:"pragmaHeader,omitempty"` + EvasivePathMatch *EvasivePathMatchexp `json:"evasivePathMatch,omitempty"` } `json:"securityPolicies"` Siem *Siemexp `json:"siem,omitempty"` AdvancedOptions *AdvancedOptionsexp `json:"advancedOptions,omitempty"` @@ -459,8 +461,9 @@ type ( // AdvancedOptionsexp is returned as part of GetExportConfigurationResponse. AdvancedOptionsexp struct { - Logging *Loggingexp `json:"logging"` - Prefetch struct { + Logging *Loggingexp `json:"logging"` + EvasivePathMatch *EvasivePathMatchexp `json:"evasivePathMatch,omitempty"` + Prefetch struct { AllExtensions bool `json:"allExtensions"` EnableAppLayer bool `json:"enableAppLayer"` EnableRateControls bool `json:"enableRateControls"` @@ -608,6 +611,11 @@ type ( } `json:"standardHeaders"` } + // EvasivePathMatchexp contains the EnablePathMatch setting + EvasivePathMatchexp struct { + EnablePathMatch bool `json:"enabled"` + } + // ConditionsExp is returned as part of GetExportConfigurationResponse. ConditionsExp []struct { Type string `json:"type"` diff --git a/pkg/networklists/network_list.go b/pkg/networklists/network_list.go index 066259a7..4ec2f5e8 100644 --- a/pkg/networklists/network_list.go +++ b/pkg/networklists/network_list.go @@ -43,26 +43,31 @@ type ( // GetNetworkListsResponse contains response from GetNetworkLists method GetNetworkListsResponse struct { - Links *NetworkListsResponseLinks `json:"links,omitempty"` - NetworkLists []struct { - ElementCount int `json:"elementCount"` - Links *NetworkListsLinks `json:"links,omitempty"` - Name string `json:"name"` - NetworkListType string `json:"networkListType"` - ReadOnly bool `json:"readOnly"` - Shared bool `json:"shared"` - SyncPoint int `json:"syncPoint"` - Type string `json:"type"` - UniqueID string `json:"uniqueId"` - AccessControlGroup string `json:"accessControlGroup,omitempty"` - Description string `json:"description,omitempty"` - } `json:"networkLists"` + Links *NetworkListsResponseLinks `json:"links,omitempty"` + NetworkLists []GetNetworkListsResponseListElement `json:"networkLists"` + } + + // GetNetworkListsResponseListElement contains information about a single network list + GetNetworkListsResponseListElement struct { + ElementCount int `json:"elementCount"` + Links *NetworkListsLinks `json:"links,omitempty"` + Name string `json:"name"` + NetworkListType string `json:"networkListType"` + ReadOnly bool `json:"readOnly"` + Shared bool `json:"shared"` + SyncPoint int `json:"syncPoint"` + Type string `json:"type"` + UniqueID string `json:"uniqueId"` + AccessControlGroup string `json:"accessControlGroup,omitempty"` + Description string `json:"description,omitempty"` } // GetNetworkListResponse contains response from GetNetworkList method GetNetworkListResponse struct { Name string `json:"name"` UniqueID string `json:"uniqueId"` + ContractID string `json:"contractId"` + GroupID int `json:"groupId"` SyncPoint int `json:"syncPoint"` Type string `json:"type"` Description string `json:"description,omitempty"` @@ -338,6 +343,7 @@ func (p *networklists) GetNetworkLists(ctx context.Context, params GetNetworkLis return &rval, nil } + rvalfiltered.Links = rval.Links for _, val := range rval.NetworkLists { if (params.Name == "" || params.Name == val.Name) && (params.Type == "" || params.Type == val.Type) { rvalfiltered.NetworkLists = append(rvalfiltered.NetworkLists, val) diff --git a/pkg/networklists/network_list_test.go b/pkg/networklists/network_list_test.go index 4411a45d..2ff784ee 100644 --- a/pkg/networklists/network_list_test.go +++ b/pkg/networklists/network_list_test.go @@ -86,6 +86,83 @@ func TestNetworkList_ListNetworkList(t *testing.T) { } } +func TestNetworkList_FilterNetworkLists(t *testing.T) { + + result := GetNetworkListsResponse{} + + respData := compactJSON(loadFixtureBytes("testdata/TestNetworkList/NetworkLists.json")) + json.Unmarshal([]byte(respData), &result) + + expectedResult := GetNetworkListsResponse{} + expectedResponseData := compactJSON(loadFixtureBytes("testdata/TestNetworkList/NetworkLists_GEO.json")) + json.Unmarshal([]byte(expectedResponseData), &expectedResult) + + tests := map[string]struct { + params GetNetworkListsRequest + responseStatus int + responseBody string + expectedPath string + expectedResponse *GetNetworkListsResponse + withError error + headers http.Header + }{ + "200 OK": { + params: GetNetworkListsRequest{Type: "GEO"}, + headers: http.Header{ + "Content-Type": []string{"application/json"}, + }, + responseStatus: http.StatusOK, + responseBody: respData, + expectedPath: "/network-list/v2/network-lists", + expectedResponse: &expectedResult, + }, + "500 internal server error": { + params: GetNetworkListsRequest{}, + headers: http.Header{}, + responseStatus: http.StatusInternalServerError, + responseBody: ` +{ + "type": "internal_error", + "title": "Internal Server Error", + "detail": "Error fetching networklist", + "status": 500 +}`, + expectedPath: "/network-list/v2/network-lists", + withError: &Error{ + Type: "internal_error", + Title: "Internal Server Error", + Detail: "Error fetching networklist", + StatusCode: http.StatusInternalServerError, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, test.expectedPath, r.URL.String()) + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(test.responseStatus) + _, err := w.Write([]byte(test.responseBody)) + assert.NoError(t, err) + })) + client := mockAPIClient(t, mockServer) + result, err := client.GetNetworkLists( + session.ContextWithOptions( + context.Background(), + session.WithContextHeaders(test.headers), + ), + test.params) + if test.withError != nil { + assert.True(t, errors.Is(err, test.withError), "want: %s; got: %s", test.withError, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedResponse, result) + }) + } +} + // Test NetworkList func TestNetworkList_GetNetworkList(t *testing.T) { diff --git a/pkg/networklists/testdata/TestNetworkList/NetworkLists_GEO.json b/pkg/networklists/testdata/TestNetworkList/NetworkLists_GEO.json new file mode 100644 index 00000000..affd05e8 --- /dev/null +++ b/pkg/networklists/testdata/TestNetworkList/NetworkLists_GEO.json @@ -0,0 +1,996 @@ +{ + "links": { + "create": { + "href": "/network-list/v2/network-lists", + "method": "POST" + } + }, + "networkLists": [ + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/65654_GEONETWORKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/65654_GEONETWORKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/65654_GEONETWORKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/65654_GEONETWORKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/65654_GEONETWORKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/65654_GEONETWORKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/65654_GEONETWORKLIST", + "method": "PUT" + } + }, + "name": "Geo - Network List", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "65654_GEONETWORKLIST" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/63707_EGPGEOLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/63707_EGPGEOLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/63707_EGPGEOLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/63707_EGPGEOLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/63707_EGPGEOLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/63707_EGPGEOLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/63707_EGPGEOLIST", + "method": "PUT" + } + }, + "name": "EGP Geolist", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "63707_EGPGEOLIST" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/55213_GEOBLACKLISTGCO/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/55213_GEOBLACKLISTGCO/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/55213_GEOBLACKLISTGCO/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/55213_GEOBLACKLISTGCO" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/55213_GEOBLACKLISTGCO/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/55213_GEOBLACKLISTGCO/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/55213_GEOBLACKLISTGCO", + "method": "PUT" + } + }, + "name": "Geo Blacklist - GCO", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "55213_GEOBLACKLISTGCO" + }, + { + "accessControlGroup": "ALM - 3-TE78Q.G112653", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/53348_GEOBLACKLISTJENKINS/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/53348_GEOBLACKLISTJENKINS/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/53348_GEOBLACKLISTJENKINS/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/53348_GEOBLACKLISTJENKINS" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/53348_GEOBLACKLISTJENKINS/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/53348_GEOBLACKLISTJENKINS/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/53348_GEOBLACKLISTJENKINS", + "method": "PUT" + } + }, + "name": "GEO Blacklist_Jenkins", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "53348_GEOBLACKLISTJENKINS" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/53076_EXPEDIAGROUPGEOLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/53076_EXPEDIAGROUPGEOLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/53076_EXPEDIAGROUPGEOLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/53076_EXPEDIAGROUPGEOLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/53076_EXPEDIAGROUPGEOLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/53076_EXPEDIAGROUPGEOLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/53076_EXPEDIAGROUPGEOLIST", + "method": "PUT" + } + }, + "name": "Expediagroup GEO list", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "53076_EXPEDIAGROUPGEOLIST" + }, + { + "accessControlGroup": "Car Rentals - 3-TE78Q.G116804", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/52783_APIGEOBLOCK/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/52783_APIGEOBLOCK/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/52783_APIGEOBLOCK/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/52783_APIGEOBLOCK" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/52783_APIGEOBLOCK/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/52783_APIGEOBLOCK/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/52783_APIGEOBLOCK", + "method": "PUT" + } + }, + "name": "API_GEOBlock", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "52783_APIGEOBLOCK" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/52767_GEOBLACKLISTDUO/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/52767_GEOBLACKLISTDUO/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/52767_GEOBLACKLISTDUO/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/52767_GEOBLACKLISTDUO" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/52767_GEOBLACKLISTDUO/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/52767_GEOBLACKLISTDUO/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/52767_GEOBLACKLISTDUO", + "method": "PUT" + } + }, + "name": "Geo Blacklist - Duo", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "52767_GEOBLACKLISTDUO" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/51561_EGENCIAGEOBLOCK/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/51561_EGENCIAGEOBLOCK/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/51561_EGENCIAGEOBLOCK/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/51561_EGENCIAGEOBLOCK" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/51561_EGENCIAGEOBLOCK/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/51561_EGENCIAGEOBLOCK/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/51561_EGENCIAGEOBLOCK", + "method": "PUT" + } + }, + "name": "Egencia GEO Block", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "51561_EGENCIAGEOBLOCK" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 10, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/50337_HOTEISCOMGEOLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/50337_HOTEISCOMGEOLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/50337_HOTEISCOMGEOLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/50337_HOTEISCOMGEOLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/50337_HOTEISCOMGEOLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/50337_HOTEISCOMGEOLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/50337_HOTEISCOMGEOLIST", + "method": "PUT" + } + }, + "name": "hoteis.com GEO list", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 1, + "type": "GEO", + "uniqueId": "50337_HOTEISCOMGEOLIST" + }, + { + "accessControlGroup": "EGENCIA - 3-TE78Q.G122736", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/49997_LEGACYORBITZBLACKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/49997_LEGACYORBITZBLACKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/49997_LEGACYORBITZBLACKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/49997_LEGACYORBITZBLACKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/49997_LEGACYORBITZBLACKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/49997_LEGACYORBITZBLACKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/49997_LEGACYORBITZBLACKLIST", + "method": "PUT" + } + }, + "name": "LegacyOrbitz GEO BlackList", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "49997_LEGACYORBITZBLACKLIST" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/49182_ADTGEOBLACKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/49182_ADTGEOBLACKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/49182_ADTGEOBLACKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/49182_ADTGEOBLACKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/49182_ADTGEOBLACKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/49182_ADTGEOBLACKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/49182_ADTGEOBLACKLIST", + "method": "PUT" + } + }, + "name": "ADT - Geo Blacklist", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "49182_ADTGEOBLACKLIST" + }, + { + "accessControlGroup": " Kona Site Defender - C-D5TW8R - C-D5TW8R.G31325", + "elementCount": 1, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/47882_HOTELSBRANDGEOLISTUSED/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/47882_HOTELSBRANDGEOLISTUSED/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/47882_HOTELSBRANDGEOLISTUSED/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/47882_HOTELSBRANDGEOLISTUSED" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/47882_HOTELSBRANDGEOLISTUSED/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/47882_HOTELSBRANDGEOLISTUSED/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/47882_HOTELSBRANDGEOLISTUSED", + "method": "PUT" + } + }, + "name": "Hotels Brand GEO List (Used by Custom Bots)", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 1, + "type": "GEO", + "uniqueId": "47882_HOTELSBRANDGEOLISTUSED" + }, + { + "accessControlGroup": "Car Rentals - 3-TE78Q.G116804", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/46790_CARRENTALSGEOBLOCK/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/46790_CARRENTALSGEOBLOCK/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/46790_CARRENTALSGEOBLOCK/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/46790_CARRENTALSGEOBLOCK" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/46790_CARRENTALSGEOBLOCK/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/46790_CARRENTALSGEOBLOCK/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/46790_CARRENTALSGEOBLOCK", + "method": "PUT" + } + }, + "name": "CarRentals Geo Block", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "46790_CARRENTALSGEOBLOCK" + }, + { + "accessControlGroup": "EPC - 3-TE78Q.G82853", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/45956_EPCGEOBLACKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/45956_EPCGEOBLACKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/45956_EPCGEOBLACKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/45956_EPCGEOBLACKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/45956_EPCGEOBLACKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/45956_EPCGEOBLACKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/45956_EPCGEOBLACKLIST", + "method": "PUT" + } + }, + "name": "EPC GEO Blacklist", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "45956_EPCGEOBLACKLIST" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/44831_ECSCGEOBLACKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/44831_ECSCGEOBLACKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/44831_ECSCGEOBLACKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/44831_ECSCGEOBLACKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/44831_ECSCGEOBLACKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/44831_ECSCGEOBLACKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/44831_ECSCGEOBLACKLIST", + "method": "PUT" + } + }, + "name": "ECSC-GEO Blacklist", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "44831_ECSCGEOBLACKLIST" + }, + { + "accessControlGroup": "exped_mk_101288 - 3-TE78Q.G112742", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/42687_HOTELSBRANDGEOBLACKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/42687_HOTELSBRANDGEOBLACKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/42687_HOTELSBRANDGEOBLACKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/42687_HOTELSBRANDGEOBLACKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/42687_HOTELSBRANDGEOBLACKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/42687_HOTELSBRANDGEOBLACKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/42687_HOTELSBRANDGEOBLACKLIST", + "method": "PUT" + } + }, + "name": "Hotels Brand Geo Blacklist", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "42687_HOTELSBRANDGEOBLACKLIST" + }, + { + "accessControlGroup": "EGENCIA - 3-TE78Q.G122736", + "elementCount": 1, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/40731_BMROLLOUTGEO/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/40731_BMROLLOUTGEO/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/40731_BMROLLOUTGEO/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/40731_BMROLLOUTGEO" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/40731_BMROLLOUTGEO/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/40731_BMROLLOUTGEO/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/40731_BMROLLOUTGEO", + "method": "PUT" + } + }, + "name": "BM rollout-GEO", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 8, + "type": "GEO", + "uniqueId": "40731_BMROLLOUTGEO" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/40713_BMROLLOUTGEO/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/40713_BMROLLOUTGEO/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/40713_BMROLLOUTGEO/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/40713_BMROLLOUTGEO" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/40713_BMROLLOUTGEO/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/40713_BMROLLOUTGEO/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/40713_BMROLLOUTGEO", + "method": "PUT" + } + }, + "name": "Not Used_3-TE78Q ION", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 4, + "type": "GEO", + "uniqueId": "40713_BMROLLOUTGEO" + }, + { + "accessControlGroup": "3-TE78Q ION - 3-TE78Q.G17244", + "description": "case F-CS-2759696", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/40548_GEOBLOCKHOTWIRE/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/40548_GEOBLOCKHOTWIRE/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/40548_GEOBLOCKHOTWIRE/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/40548_GEOBLOCKHOTWIRE" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/40548_GEOBLOCKHOTWIRE/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/40548_GEOBLOCKHOTWIRE/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/40548_GEOBLOCKHOTWIRE", + "method": "PUT" + } + }, + "name": "GEO-Block Hotwire", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 2, + "type": "GEO", + "uniqueId": "40548_GEOBLOCKHOTWIRE" + }, + { + "accessControlGroup": "EGENCIA - 3-TE78Q.G122736", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/40016_TEMPGEOBLOCKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/40016_TEMPGEOBLOCKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/40016_TEMPGEOBLOCKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/40016_TEMPGEOBLOCKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/40016_TEMPGEOBLOCKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/40016_TEMPGEOBLOCKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/40016_TEMPGEOBLOCKLIST", + "method": "PUT" + } + }, + "name": "TEMP Geo-Block List", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "40016_TEMPGEOBLOCKLIST" + }, + { + "accessControlGroup": "EGENCIA - 3-TE78Q.G122736", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/25065_HOMEAWAYGEOBLOCK/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/25065_HOMEAWAYGEOBLOCK/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/25065_HOMEAWAYGEOBLOCK/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/25065_HOMEAWAYGEOBLOCK" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/25065_HOMEAWAYGEOBLOCK/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/25065_HOMEAWAYGEOBLOCK/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/25065_HOMEAWAYGEOBLOCK", + "method": "PUT" + } + }, + "name": "HomeAway Geo Block", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "25065_HOMEAWAYGEOBLOCK" + }, + { + "accessControlGroup": "EGENCIA - 3-TE78Q.G122736", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/17401_CLASSICVACATIONSGEOBLACK/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/17401_CLASSICVACATIONSGEOBLACK/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/17401_CLASSICVACATIONSGEOBLACK/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/17401_CLASSICVACATIONSGEOBLACK" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/17401_CLASSICVACATIONSGEOBLACK/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/17401_CLASSICVACATIONSGEOBLACK/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/17401_CLASSICVACATIONSGEOBLACK", + "method": "PUT" + } + }, + "name": "Classic vacations Geo Black list", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "17401_CLASSICVACATIONSGEOBLACK" + }, + { + "accessControlGroup": " Kona Site Defender - C-D5TW8R - C-D5TW8R.G31325", + "elementCount": 0, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/16865_EANGEOBLACKLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/16865_EANGEOBLACKLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/16865_EANGEOBLACKLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/16865_EANGEOBLACKLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/16865_EANGEOBLACKLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/16865_EANGEOBLACKLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/16865_EANGEOBLACKLIST", + "method": "PUT" + } + }, + "name": "EAN Geo Blacklist", + "networkListType": "networkListResponse", + "readOnly": false, + "shared": false, + "syncPoint": 0, + "type": "GEO", + "uniqueId": "16865_EANGEOBLACKLIST" + }, + { + "elementCount": 27, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/968_ITARCOUNTRYLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/968_ITARCOUNTRYLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/968_ITARCOUNTRYLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/968_ITARCOUNTRYLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/968_ITARCOUNTRYLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/968_ITARCOUNTRYLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/968_ITARCOUNTRYLIST", + "method": "PUT" + } + }, + "name": "International Traffic in Arms Regulations (ITAR) List", + "networkListType": "networkListResponse", + "readOnly": true, + "shared": true, + "syncPoint": 9, + "type": "GEO", + "uniqueId": "968_ITARCOUNTRYLIST" + }, + { + "elementCount": 22, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/969_IEEPACOUNTRYLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/969_IEEPACOUNTRYLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/969_IEEPACOUNTRYLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/969_IEEPACOUNTRYLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/969_IEEPACOUNTRYLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/969_IEEPACOUNTRYLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/969_IEEPACOUNTRYLIST", + "method": "PUT" + } + }, + "name": "International Emergency Economic Powers Act (IEEPA) List", + "networkListType": "networkListResponse", + "readOnly": true, + "shared": true, + "syncPoint": 3, + "type": "GEO", + "uniqueId": "969_IEEPACOUNTRYLIST" + }, + { + "elementCount": 18, + "links": { + "activateInProduction": { + "href": "/network-list/v2/network-lists/967_OFACCOUNTRYLIST/environments/PRODUCTION/activate", + "method": "POST" + }, + "activateInStaging": { + "href": "/network-list/v2/network-lists/967_OFACCOUNTRYLIST/environments/STAGING/activate", + "method": "POST" + }, + "appendItems": { + "href": "/network-list/v2/network-lists/967_OFACCOUNTRYLIST/append", + "method": "POST" + }, + "retrieve": { + "href": "/network-list/v2/network-lists/967_OFACCOUNTRYLIST" + }, + "statusInProduction": { + "href": "/network-list/v2/network-lists/967_OFACCOUNTRYLIST/environments/PRODUCTION/status" + }, + "statusInStaging": { + "href": "/network-list/v2/network-lists/967_OFACCOUNTRYLIST/environments/STAGING/status" + }, + "update": { + "href": "/network-list/v2/network-lists/967_OFACCOUNTRYLIST", + "method": "PUT" + } + }, + "name": "Office of Foreign Asset Control (OFAC) List", + "networkListType": "networkListResponse", + "readOnly": true, + "shared": true, + "syncPoint": 4, + "type": "GEO", + "uniqueId": "967_OFACCOUNTRYLIST" + } + ] +} From e15e2ca56c632f7a10d3c7663cd602b724f31541 Mon Sep 17 00:00:00 2001 From: "Zagrajczuk, Wojciech" Date: Tue, 18 Jan 2022 15:28:10 +0100 Subject: [PATCH 02/17] DXE-368 Unifing usage of Ozzo-verification library --- go.mod | 1 - go.sum | 13 ------------- pkg/cps/changes.go | 2 +- pkg/iam/user.go | 2 +- pkg/networklists/activations.go | 2 +- pkg/networklists/network_list.go | 2 +- pkg/networklists/network_list_description.go | 2 +- 7 files changed, 5 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index fd85d5a4..0b9904bc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.14 require ( github.com/apex/log v1.9.0 - github.com/go-ozzo/ozzo-validation v3.6.0+incompatible github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/google/uuid v1.1.1 github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 3d715acc..65046146 100644 --- a/go.sum +++ b/go.sum @@ -14,10 +14,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= -github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= -github.com/go-ozzo/ozzo-validation/v4 v4.2.2 h1:5uhbQAuRK6taB9orHJXA5GtOCuQbsHktskg8aWciC68= -github.com/go-ozzo/ozzo-validation/v4 v4.2.2/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -32,10 +28,8 @@ github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgb github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -50,7 +44,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -58,7 +51,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= @@ -70,9 +62,7 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -98,9 +88,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -109,7 +97,6 @@ gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= diff --git a/pkg/cps/changes.go b/pkg/cps/changes.go index 67349a7c..d737966c 100644 --- a/pkg/cps/changes.go +++ b/pkg/cps/changes.go @@ -7,7 +7,7 @@ import ( "net/http" "net/url" - validation "github.com/go-ozzo/ozzo-validation" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( diff --git a/pkg/iam/user.go b/pkg/iam/user.go index b15b27f1..d33e7a58 100644 --- a/pkg/iam/user.go +++ b/pkg/iam/user.go @@ -7,7 +7,7 @@ import ( "net/url" "path" - validation "github.com/go-ozzo/ozzo-validation" + validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" "github.com/spf13/cast" ) diff --git a/pkg/networklists/activations.go b/pkg/networklists/activations.go index d35d4418..39056f10 100644 --- a/pkg/networklists/activations.go +++ b/pkg/networklists/activations.go @@ -6,7 +6,7 @@ import ( "net/http" "time" - validation "github.com/go-ozzo/ozzo-validation" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( diff --git a/pkg/networklists/network_list.go b/pkg/networklists/network_list.go index 4ec2f5e8..6577b8fe 100644 --- a/pkg/networklists/network_list.go +++ b/pkg/networklists/network_list.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - validation "github.com/go-ozzo/ozzo-validation" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( diff --git a/pkg/networklists/network_list_description.go b/pkg/networklists/network_list_description.go index c3f189ea..ab163656 100644 --- a/pkg/networklists/network_list_description.go +++ b/pkg/networklists/network_list_description.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - validation "github.com/go-ozzo/ozzo-validation" + validation "github.com/go-ozzo/ozzo-validation/v4" ) type ( From db105441865d3b7bd311bb663da56908263b35ef Mon Sep 17 00:00:00 2001 From: Darek Stopka Date: Fri, 26 Nov 2021 17:11:12 +0100 Subject: [PATCH 03/17] DXE-43 Add support for visitor_prioritization (VP) cloudlet --- pkg/cloudlets/match_rule.go | 536 +++++++++++++++++++++++++++ pkg/cloudlets/match_rule_test.go | 520 ++++++++++++++++++++++++++ pkg/cloudlets/policy_version.go | 432 --------------------- pkg/cloudlets/policy_version_test.go | 469 +++++++++-------------- 4 files changed, 1235 insertions(+), 722 deletions(-) create mode 100644 pkg/cloudlets/match_rule.go create mode 100644 pkg/cloudlets/match_rule_test.go diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go new file mode 100644 index 00000000..7e4f2b1a --- /dev/null +++ b/pkg/cloudlets/match_rule.go @@ -0,0 +1,536 @@ +package cloudlets + +import ( + "encoding/json" + "errors" + "fmt" + + validation "github.com/go-ozzo/ozzo-validation/v4" +) + +type ( + // MatchRule is base interface for MarchRuleALB and MatchRuleER + MatchRule interface { + // cloudletType is a private method to ensure that only match rules for supported cloudlets can be used + cloudletType() string + Validate() error + } + + // MatchRules is an array of *MarchRuleALB or *MatchRuleER depending on the cloudletId (9 or 0) of the policy + MatchRules []MatchRule + + // MatchRuleALB represents a match rule resource for create or update resource + MatchRuleALB struct { + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaALB `json:"matches,omitempty"` + MatchURL string `json:"matchURL,omitempty"` + MatchesAlways bool `json:"matchesAlways"` + ForwardSettings ForwardSettings `json:"forwardSettings"` + Disabled bool `json:"disabled,omitempty"` + } + + // ForwardSettings represents forward settings + ForwardSettings struct { + OriginID string `json:"originId"` + } + + // MatchRuleER represents a match rule resource for create or update resource + MatchRuleER struct { + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaER `json:"matches,omitempty"` + UseRelativeURL string `json:"useRelativeUrl,omitempty"` + StatusCode int `json:"statusCode"` + RedirectURL string `json:"redirectURL"` + MatchURL string `json:"matchURL,omitempty"` + UseIncomingQueryString bool `json:"useIncomingQueryString"` + UseIncomingSchemeAndHost bool `json:"useIncomingSchemeAndHost"` + Disabled bool `json:"disabled,omitempty"` + } + + // MatchRuleVP represents a match rule resource for create or update resource + MatchRuleVP struct { + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaVP `json:"matches,omitempty"` + MatchURL string `json:"matchURL,omitempty"` + UseIncomingQueryString bool `json:"useIncomingQueryString,omitempty"` + PassThroughPercent float64 `json:"passThroughPercent"` + Disabled bool `json:"disabled,omitempty"` + } + + // MatchCriteria represents a match criteria resource for match rule for cloudlet + MatchCriteria struct { + MatchType string `json:"matchType,omitempty"` + MatchValue string `json:"matchValue,omitempty"` + MatchOperator MatchOperator `json:"matchOperator,omitempty"` + CaseSensitive bool `json:"caseSensitive"` + Negate bool `json:"negate"` + CheckIPs CheckIPs `json:"checkIPs,omitempty"` + ObjectMatchValue interface{} `json:"objectMatchValue,omitempty"` + } + + // MatchCriteriaALB represents a match criteria resource for match rule for cloudlet ALB + // ObjectMatchValue can contain ObjectMatchValueObject, ObjectMatchValueSimple or ObjectMatchValueRange + MatchCriteriaALB MatchCriteria + + // MatchCriteriaER represents a match criteria resource for match rule for cloudlet ER + // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple + MatchCriteriaER MatchCriteria + + // MatchCriteriaVP represents a match criteria resource for match rule for cloudlet VP + // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple + MatchCriteriaVP MatchCriteria + + // ObjectMatchValueObject represents an object match value resource for match criteria of type object + ObjectMatchValueObject struct { + Name string `json:"name"` + Type ObjectMatchValueObjectType `json:"type"` + NameCaseSensitive bool `json:"nameCaseSensitive"` + NameHasWildcard bool `json:"nameHasWildcard"` + Options *Options `json:"options,omitempty"` + } + + // ObjectMatchValueSimple represents an object match value resource for match criteria of type simple + ObjectMatchValueSimple struct { + Type ObjectMatchValueSimpleType `json:"type"` + Value []string `json:"value,omitempty"` + } + + // ObjectMatchValueRange represents an object match value resource for match criteria of type range + ObjectMatchValueRange struct { + Type ObjectMatchValueRangeType `json:"type"` + Value []int64 `json:"value,omitempty"` + } + + // Options represents an option resource for ObjectMatchValueObject + Options struct { + Value []string `json:"value,omitempty"` + ValueHasWildcard bool `json:"valueHasWildcard,omitempty"` + ValueCaseSensitive bool `json:"valueCaseSensitive,omitempty"` + ValueEscaped bool `json:"valueEscaped,omitempty"` + } + + //MatchRuleType enum type + MatchRuleType string + // MatchRuleFormat enum type + MatchRuleFormat string + // MatchOperator enum type + MatchOperator string + // CheckIPs enum type + CheckIPs string + // ObjectMatchValueRangeType enum type + ObjectMatchValueRangeType string + // ObjectMatchValueSimpleType enum type + ObjectMatchValueSimpleType string + // ObjectMatchValueObjectType enum type + ObjectMatchValueObjectType string +) + +const ( + // MatchRuleTypeALB represents rule type for ALB cloudlets + MatchRuleTypeALB MatchRuleType = "albMatchRule" + // MatchRuleTypeER represents rule type for ER cloudlets + MatchRuleTypeER MatchRuleType = "erMatchRule" + // MatchRuleTypeVP represents rule type for VP cloudlets + MatchRuleTypeVP MatchRuleType = "vpMatchRule" +) + +const ( + // MatchRuleFormat10 represents default match rule format + MatchRuleFormat10 MatchRuleFormat = "1.0" + // MatchRuleFormatDefault represents default match rule format + MatchRuleFormatDefault = MatchRuleFormat10 +) + +const ( + // MatchOperatorContains represents contains operator + MatchOperatorContains MatchOperator = "contains" + // MatchOperatorExists represents exists operator + MatchOperatorExists MatchOperator = "exists" + // MatchOperatorEquals represents equals operator + MatchOperatorEquals MatchOperator = "equals" +) + +const ( + // CheckIPsConnectingIP represents connecting ip option + CheckIPsConnectingIP CheckIPs = "CONNECTING_IP" + // CheckIPsXFFHeaders represents xff headers option + CheckIPsXFFHeaders CheckIPs = "XFF_HEADERS" + // CheckIPsConnectingIPXFFHeaders represents connecting ip + xff headers option + CheckIPsConnectingIPXFFHeaders CheckIPs = "CONNECTING_IP XFF_HEADERS" +) + +const ( + // Range represents range option + Range ObjectMatchValueRangeType = "range" + // Simple represents simple option + Simple ObjectMatchValueSimpleType = "simple" + // Object represents object option + Object ObjectMatchValueObjectType = "object" +) + +// Validate validates MatchRuleALB +func (m MatchRuleALB) Validate() error { + return validation.Errors{ + "Type": validation.Validate(m.Type, validation.In(MatchRuleTypeALB).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'albMatchRule' or '' (empty)", (&m).Type))), + "Name": validation.Validate(m.Name, validation.Length(0, 8192)), + "Start": validation.Validate(m.Start, validation.Min(0)), + "End": validation.Validate(m.End, validation.Min(0)), + "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), + "ForwardSettings.OriginID": validation.Validate(m.ForwardSettings.OriginID, validation.Required, validation.Length(0, 8192)), + "Matches": validation.Validate(m.Matches), + }.Filter() +} + +// Validate validates MatchRuleER +func (m MatchRuleER) Validate() error { + return validation.Errors{ + "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeER).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'erMatchRule' or '' (empty)", (&m).Type))), + "Name": validation.Validate(m.Name, validation.Length(0, 8192)), + "Start": validation.Validate(m.Start, validation.Min(0)), + "End": validation.Validate(m.End, validation.Min(0)), + "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), + "RedirectURL": validation.Validate(m.RedirectURL, validation.Required, validation.Length(1, 8192)), + "UseRelativeURL": validation.Validate(m.UseRelativeURL, validation.In("none", "copy_scheme_hostname", "relative_url").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'none', 'copy_scheme_hostname', 'relative_url' or '' (empty)", (&m).UseRelativeURL))), + "StatusCode": validation.Validate(m.StatusCode, validation.Required, validation.In(301, 302, 303, 307, 308).Error( + fmt.Sprintf("value '%d' is invalid. Must be one of: 301, 302, 303, 307 or 308", (&m).StatusCode))), + "Matches": validation.Validate(m.Matches), + }.Filter() +} + +// Validate validates MatchRuleVP +func (m MatchRuleVP) Validate() error { + return validation.Errors{ + "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeVP).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'vpMatchRule' or '' (empty)", (&m).Type))), + "Name": validation.Validate(m.Name, validation.Length(0, 8192)), + "Start": validation.Validate(m.Start, validation.Min(0)), + "End": validation.Validate(m.End, validation.Min(0)), + "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), + "PassThroughPercent": validation.Validate(m.PassThroughPercent, validation.Required, validation.Min(-1.0), validation.Max(100.0)), + "Matches": validation.Validate(m.Matches), + }.Filter() +} + +// Validate validates MatchCriteriaALB +func (m MatchCriteriaALB) Validate() error { + return validation.Errors{ + "MatchType": validation.Validate(m.MatchType, validation.In("clientip", "continent", "cookie", "countrycode", + "deviceCharacteristics", "extension", "header", "hostname", "method", "path", "protocol", "proxy", "query", "regioncode", "range").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'clientip', 'continent', 'cookie', 'countrycode', 'deviceCharacteristics', "+ + "'extension', 'header', 'hostname', 'method', 'path', 'protocol', 'proxy', 'query', 'regioncode', 'range' or '' (empty)", (&m).MatchType))), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192), validation.Required.When(m.ObjectMatchValue == nil)), + "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), + "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrRangeOrObjectValidation)), + }.Filter() +} + +// Validate validates MatchCriteriaER +func (m MatchCriteriaER) Validate() error { + return validation.Errors{ + "MatchType": validation.Validate(m.MatchType, validation.In("header", "hostname", "path", "extension", "query", + "regex", "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'regex', 'cookie', "+ + "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy' or '' (empty)", (&m).MatchType))), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), + "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + }.Filter() +} + +// Validate validates MatchCriteriaVP +func (m MatchCriteriaVP) Validate() error { + return validation.Errors{ + "MatchType": validation.Validate(m.MatchType, validation.In("header", "hostname", "path", "extension", "query", + "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'cookie', "+ + "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy' or '' (empty)", (&m).MatchType))), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), + "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + }.Filter() +} + +func objectMatchValueSimpleOrObjectValidation(value interface{}) error { + if value == nil { + return nil + } + switch value.(type) { + case *ObjectMatchValueObject, *ObjectMatchValueSimple: + return nil + default: + return fmt.Errorf("type %T is invalid. Must be one of: 'simple' or 'object'", value) + } +} + +func objectMatchValueSimpleOrRangeOrObjectValidation(value interface{}) error { + if value == nil { + return nil + } + switch value.(type) { + case *ObjectMatchValueObject, *ObjectMatchValueSimple, *ObjectMatchValueRange: + return nil + default: + return fmt.Errorf("type %T is invalid. Must be one of: 'simple', 'range' or 'object'", value) + } +} + +// Validate validates ObjectMatchValueRange +func (o ObjectMatchValueRange) Validate() error { + return validation.Errors{ + "Type": validation.Validate(o.Type, validation.In(Range).Error( + fmt.Sprintf("value '%s' is invalid. Must be: 'range'", (&o).Type))), + }.Filter() +} + +// Validate validates ObjectMatchValueSimple +func (o ObjectMatchValueSimple) Validate() error { + return validation.Errors{ + "Type": validation.Validate(o.Type, validation.In(Simple).Error( + fmt.Sprintf("value '%s' is invalid. Must be: 'simple'", (&o).Type))), + }.Filter() +} + +// Validate validates ObjectMatchValueObject +func (o ObjectMatchValueObject) Validate() error { + return validation.Errors{ + "Name": validation.Validate(o.Name, validation.Required, validation.Length(0, 8192)), + "Type": validation.Validate(o.Type, validation.Required, validation.In(Object).Error( + fmt.Sprintf("value '%s' is invalid. Must be: 'object'", (&o).Type))), + }.Filter() +} + +var ( + // ErrUnmarshallMatchCriteriaALB is returned when unmarshalling of MatchCriteriaALB fails + ErrUnmarshallMatchCriteriaALB = errors.New("unmarshalling MatchCriteriaALB") + // ErrUnmarshallMatchCriteriaER is returned when unmarshalling of MatchCriteriaER fails + ErrUnmarshallMatchCriteriaER = errors.New("unmarshalling MatchCriteriaER") + // ErrUnmarshallMatchCriteriaVP is returned when unmarshalling of MatchCriteriaVP fails + ErrUnmarshallMatchCriteriaVP = errors.New("unmarshalling MatchCriteriaVP") + // ErrUnmarshallMatchRules is returned when unmarshalling of MatchRules fails + ErrUnmarshallMatchRules = errors.New("unmarshalling MatchRules") +) + +func (m MatchRuleALB) cloudletType() string { + return "albMatchRule" +} + +func (m MatchRuleER) cloudletType() string { + return "erMatchRule" +} + +func (m MatchRuleVP) cloudletType() string { + return "vpMatchRule" +} + +// matchRuleHandlers contains mapping between name of the type for MatchRule and its implementation +// It makes the UnmarshalJSON more compact and easier to support more cloudlet types +var matchRuleHandlers = map[string]func() MatchRule{ + "albMatchRule": func() MatchRule { return &MatchRuleALB{} }, + "erMatchRule": func() MatchRule { return &MatchRuleER{} }, + "vpMatchRule": func() MatchRule { return &MatchRuleVP{} }, +} + +// UnmarshalJSON helps to un-marshall items of MatchRules array as proper instances of *MatchRuleALB or *MatchRuleER +func (m *MatchRules) UnmarshalJSON(b []byte) error { + data := make([]map[string]interface{}, 0) + if err := json.Unmarshal(b, &data); err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchRules, err) + } + for _, matchRule := range data { + cloudletType, ok := matchRule["type"] + if !ok { + return fmt.Errorf("%w: match rule entry should contain 'type' field", ErrUnmarshallMatchRules) + } + cloudletTypeName, ok := cloudletType.(string) + if !ok { + return fmt.Errorf("%w: 'type' field on match rule entry should be a string", ErrUnmarshallMatchRules) + } + byteArr, err := json.Marshal(matchRule) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchRules, err) + } + + matchRuleType, ok := matchRuleHandlers[cloudletTypeName] + if !ok { + return fmt.Errorf("%w: unsupported match rule type: %s", ErrUnmarshallMatchRules, cloudletTypeName) + } + dst := matchRuleType() + err = json.Unmarshal(byteArr, dst) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchRules, err) + } + *m = append(*m, dst) + } + return nil +} + +// objectALBMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation +// It makes the UnmarshalJSON more compact and easier to support more types +var objectALBMatchValueHandlers = map[string]func() interface{}{ + "object": func() interface{} { return &ObjectMatchValueObject{} }, + "range": func() interface{} { return &ObjectMatchValueRange{} }, + "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +} + +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaALB as proper instance of *ObjectMatchValueObject, *ObjectMatchValueSimple or *ObjectMatchValueRange +func (m *MatchCriteriaALB) UnmarshalJSON(b []byte) error { + // matchCriteriaALB is an alias for MatchCriteriaALB for un-marshalling purposes + type matchCriteriaALB MatchCriteriaALB + + // populate common attributes using default json unmarshaler using aliased type + err := json.Unmarshal(b, (*matchCriteriaALB)(m)) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaALB, err) + } + if m.ObjectMatchValue == nil { + return nil + } + + objectMatchValueTypeName, err := getObjectMatchValueType(m.ObjectMatchValue) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaALB, err) + } + + createObjectMatchValue, ok := objectALBMatchValueHandlers[objectMatchValueTypeName] + if !ok { + return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaALB, objectMatchValueTypeName) + } + convertedObjectMatchValue, err := convertObjectMatchValue(m.ObjectMatchValue, createObjectMatchValue()) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaALB, err) + } + m.ObjectMatchValue = convertedObjectMatchValue + + return nil +} + +// objectERMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation +// It makes the UnmarshalJSON more compact and easier to support more types +var objectERMatchValueHandlers = map[string]func() interface{}{ + "object": func() interface{} { return &ObjectMatchValueObject{} }, + "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +} + +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaER as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple +func (m *MatchCriteriaER) UnmarshalJSON(b []byte) error { + // matchCriteriaER is an alias for MatchCriteriaER for un-marshalling purposes + type matchCriteriaER MatchCriteriaER + + // populate common attributes using default json unmarshaler using aliased type + err := json.Unmarshal(b, (*matchCriteriaER)(m)) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaER, err) + } + if m.ObjectMatchValue == nil { + return nil + } + + objectMatchValueTypeName, err := getObjectMatchValueType(m.ObjectMatchValue) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaER, err) + } + + createObjectMatchValue, ok := objectERMatchValueHandlers[objectMatchValueTypeName] + if !ok { + return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaER, objectMatchValueTypeName) + } + convertedObjectMatchValue, err := convertObjectMatchValue(m.ObjectMatchValue, createObjectMatchValue()) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaER, err) + } + m.ObjectMatchValue = convertedObjectMatchValue + + return nil +} + +// objectVPMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation +// It makes the UnmarshalJSON more compact and easier to support more types +var objectVPMatchValueHandlers = map[string]func() interface{}{ + "object": func() interface{} { return &ObjectMatchValueObject{} }, + "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +} + +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaER as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple +func (m *MatchCriteriaVP) UnmarshalJSON(b []byte) error { + // matchCriteriaER is an alias for MatchCriteriaER for un-marshalling purposes + type matchCriteriaVP MatchCriteriaVP + + // populate common attributes using default json unmarshaler using aliased type + err := json.Unmarshal(b, (*matchCriteriaVP)(m)) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaVP, err) + } + if m.ObjectMatchValue == nil { + return nil + } + + objectMatchValueTypeName, err := getObjectMatchValueType(m.ObjectMatchValue) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaVP, err) + } + + createObjectMatchValue, ok := objectVPMatchValueHandlers[objectMatchValueTypeName] + if !ok { + return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaVP, objectMatchValueTypeName) + } + convertedObjectMatchValue, err := convertObjectMatchValue(m.ObjectMatchValue, createObjectMatchValue()) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaVP, err) + } + m.ObjectMatchValue = convertedObjectMatchValue + + return nil +} + +func getObjectMatchValueType(omv interface{}) (string, error) { + objectMatchValueMap, ok := omv.(map[string]interface{}) + if !ok { + return "", fmt.Errorf("structure of objectMatchValue should be 'map', but was '%T'", omv) + } + objectMatchValueType, ok := objectMatchValueMap["type"] + if !ok { + return "", fmt.Errorf("objectMatchValue should contain 'type' field") + } + objectMatchValueTypeName, ok := objectMatchValueType.(string) + if !ok { + return "", fmt.Errorf("'type' should be a string") + } + return objectMatchValueTypeName, nil +} + +func convertObjectMatchValue(in, out interface{}) (interface{}, error) { + marshal, err := json.Marshal(in) + if err != nil { + return nil, fmt.Errorf("%s", err) + } + err = json.Unmarshal(marshal, out) + if err != nil { + return nil, fmt.Errorf("%s", err) + } + + return out, nil +} diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go new file mode 100644 index 00000000..c590dfa0 --- /dev/null +++ b/pkg/cloudlets/match_rule_test.go @@ -0,0 +1,520 @@ +package cloudlets + +import ( + "encoding/json" + "errors" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tj/assert" +) + +func TestUnmarshalJSONMatchRules(t *testing.T) { + tests := map[string]struct { + withError error + responseBody string + expectedObject MatchRules + }{ + "valid MatchRuleALB": { + responseBody: ` + [ + { + "type": "albMatchRule", + "end": 0, + "forwardSettings": { + "originId": "alb_test_krk_dc1_only" + }, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "protocol", + "matchValue": "https", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "range", + "negate": false, + "objectMatchValue": { + "type": "range", + "value": [ + 1, + 50 + ] + } + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + expectedObject: MatchRules{ + &MatchRuleALB{ + Type: "albMatchRule", + End: 0, + ForwardSettings: ForwardSettings{ + OriginID: "alb_test_krk_dc1_only", + }, + ID: 0, + MatchURL: "", + Matches: []MatchCriteriaALB{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "protocol", + MatchValue: "https", + Negate: false, + }, + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "range", + Negate: false, + ObjectMatchValue: &ObjectMatchValueRange{ + Type: "range", + Value: []int64{1, 50}, + }, + }, + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + Name: "Rule3", + Start: 0, + }, + }, + }, + + "invalid objectMatchValue type for ALB - foo": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: objectMatchValue has unexpected type: 'foo'"), + responseBody: ` + [ + { + "type": "albMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "foo", + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + + "wrong type for object value type": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: 'type' should be a string"), + responseBody: ` + [ + { + "type": "albMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": 1, + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + + "missing object value type": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: objectMatchValue should contain 'type' field"), + responseBody: ` + [ + { + "type": "albMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + + "invalid object value": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: structure of objectMatchValue should be 'map', but was 'string'"), + responseBody: ` + [ + { + "type": "albMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": "" + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + + "invalid MatchRuleAP": { + responseBody: ` + [ + { + "type": "apMatchRule" + } + ] +`, + withError: errors.New("unmarshalling MatchRules: unsupported match rule type: apMatchRule"), + }, + + "invalid type": { + withError: errors.New("unmarshalling MatchRules: 'type' field on match rule entry should be a string"), + responseBody: ` + [ + { + "type": 1 + } + ] +`, + }, + + "invalid JSON": { + withError: errors.New("unexpected end of JSON input"), + responseBody: ` + [ + { + "type": "albMatchRule" + } + +`, + }, + + "missing type": { + withError: errors.New("unmarshalling MatchRules: match rule entry should contain 'type' field"), + responseBody: ` + [ + { + } + ] +`, + }, + + "invalid objectMatchValue type for ER - range": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaER: objectMatchValue has unexpected type: 'range'"), + responseBody: ` + [ + { + "type": "erMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "range", + "value": [ + 1, + 50 + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + + "invalid objectMatchValue type for VP - range": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaVP: objectMatchValue has unexpected type: 'range'"), + responseBody: ` + [ + { + "type": "vpMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "range", + "value": [ + 1, + 50 + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + + "valid MatchRuleVP": { + responseBody: ` + [ + { + "type": "vpMatchRule", + "end": 0, + "passThroughPercent": 50.50, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "protocol", + "matchValue": "https", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + expectedObject: MatchRules{ + &MatchRuleVP{ + Type: "vpMatchRule", + End: 0, + PassThroughPercent: 50.50, + ID: 0, + MatchURL: "", + Matches: []MatchCriteriaVP{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "protocol", + MatchValue: "https", + Negate: false, + }, + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + Name: "Rule3", + Start: 0, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var matchRules MatchRules + err := json.Unmarshal([]byte(test.responseBody), &matchRules) + + if test.withError != nil { + assert.Equal(t, test.withError.Error(), err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.expectedObject, matchRules) + }) + } +} + +func TestGetObjectMatchValueType(t *testing.T) { + tests := map[string]struct { + withError error + input interface{} + expected string + }{ + "success getting objectMatchValue type": { + input: map[string]interface{}{ + "type": "range", + "value": []int{1, 50}, + }, + expected: "range", + }, + "error getting objectMatchValue type - invalid type": { + withError: errors.New("structure of objectMatchValue should be 'map', but was 'string'"), + input: "stringType", + }, + "error getting objectMatchValue type - missing type": { + withError: errors.New("objectMatchValue should contain 'type' field"), + input: map[string]interface{}{ + "value": []int{1, 50}, + }, + }, + "error getting objectMatchValue type - type not string": { + withError: errors.New("'type' should be a string"), + input: map[string]interface{}{ + "type": 50, + "value": []int{1, 50}, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + objectMatchValueType, err := getObjectMatchValueType(test.input) + + if test.withError != nil { + assert.Equal(t, test.withError.Error(), err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.expected, objectMatchValueType) + }) + } +} + +func TestConvertObjectMatchValue(t *testing.T) { + tests := map[string]struct { + withError bool + input map[string]interface{} + output interface{} + expected interface{} + }{ + "success converting objectMatchValueRange": { + input: map[string]interface{}{ + "type": "range", + "value": []int{1, 50}, + }, + output: &ObjectMatchValueRange{}, + expected: &ObjectMatchValueRange{ + Type: "range", + Value: []int64{1, 50}, + }, + }, + "success converting objectMatchValueSimple": { + input: map[string]interface{}{ + "type": "simple", + "value": []string{"GET"}, + }, + output: &ObjectMatchValueSimple{}, + expected: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + "success converting objectMatchValueObject": { + input: map[string]interface{}{ + "type": "object", + "name": "ER", + "options": map[string]interface{}{ + "value": []string{ + "text/html*", + "text/css*", + "application/x-javascript*", + }, + "valueHasWildcard": true, + }, + }, + output: &ObjectMatchValueObject{}, + expected: &ObjectMatchValueObject{ + Type: "object", + Name: "ER", + Options: &Options{ + Value: []string{ + "text/html*", + "text/css*", + "application/x-javascript*", + }, + ValueHasWildcard: true, + }, + }, + }, + "error converting objectMatchValue": { + withError: true, + input: map[string]interface{}{ + "type": "range", + "value": []int{1, 50}, + }, + output: &ObjectMatchValueSimple{}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + convertedObjectMatchValue, err := convertObjectMatchValue(test.input, test.output) + + if test.withError == true { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, test.expected, convertedObjectMatchValue) + }) + } +} diff --git a/pkg/cloudlets/policy_version.go b/pkg/cloudlets/policy_version.go index 85ae19b2..2071fabf 100644 --- a/pkg/cloudlets/policy_version.go +++ b/pkg/cloudlets/policy_version.go @@ -2,7 +2,6 @@ package cloudlets import ( "context" - "encoding/json" "errors" "fmt" "net/http" @@ -112,156 +111,6 @@ type ( PolicyID int64 Version int64 } - - // MatchRule is base interface for MarchRuleALB and MatchRuleER - MatchRule interface { - // cloudletType is a private method to ensure that only match rules for supported cloudlets can be used - cloudletType() string - Validate() error - } - - // MatchRules is an array of *MarchRuleALB or *MatchRuleER depending on the cloudletId (9 or 0) of the policy - MatchRules []MatchRule - - // MatchRuleALB represents a match rule resource for create or update resource - MatchRuleALB struct { - Name string `json:"name,omitempty"` - Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` - ID int64 `json:"id,omitempty"` - Matches []MatchCriteriaALB `json:"matches,omitempty"` - MatchURL string `json:"matchURL,omitempty"` - MatchesAlways bool `json:"matchesAlways"` - ForwardSettings ForwardSettings `json:"forwardSettings"` - Disabled bool `json:"disabled,omitempty"` - } - - // ForwardSettings represents forward settings - ForwardSettings struct { - OriginID string `json:"originId"` - } - - // MatchRuleER represents a match rule resource for create or update resource - MatchRuleER struct { - Name string `json:"name,omitempty"` - Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` - ID int64 `json:"id,omitempty"` - Matches []MatchCriteriaER `json:"matches,omitempty"` - UseRelativeURL string `json:"useRelativeUrl,omitempty"` - StatusCode int `json:"statusCode"` - RedirectURL string `json:"redirectURL"` - MatchURL string `json:"matchURL,omitempty"` - UseIncomingQueryString bool `json:"useIncomingQueryString"` - UseIncomingSchemeAndHost bool `json:"useIncomingSchemeAndHost"` - Disabled bool `json:"disabled,omitempty"` - } - - // MatchCriteria represents a match criteria resource for match rule for cloudlet - MatchCriteria struct { - MatchType string `json:"matchType,omitempty"` - MatchValue string `json:"matchValue,omitempty"` - MatchOperator MatchOperator `json:"matchOperator,omitempty"` - CaseSensitive bool `json:"caseSensitive"` - Negate bool `json:"negate"` - CheckIPs CheckIPs `json:"checkIPs,omitempty"` - ObjectMatchValue interface{} `json:"objectMatchValue,omitempty"` - } - - // MatchCriteriaALB represents a match criteria resource for match rule for cloudlet ALB - // ObjectMatchValue can contain ObjectMatchValueObject, ObjectMatchValueSimple or ObjectMatchValueRange - MatchCriteriaALB MatchCriteria - - // MatchCriteriaER represents a match criteria resource for match rule for cloudlet ER - // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple - MatchCriteriaER MatchCriteria - - // ObjectMatchValueObject represents an object match value resource for match criteria of type object - ObjectMatchValueObject struct { - Name string `json:"name"` - Type ObjectMatchValueObjectType `json:"type"` - NameCaseSensitive bool `json:"nameCaseSensitive"` - NameHasWildcard bool `json:"nameHasWildcard"` - Options *Options `json:"options,omitempty"` - } - - // ObjectMatchValueSimple represents an object match value resource for match criteria of type simple - ObjectMatchValueSimple struct { - Type ObjectMatchValueSimpleType `json:"type"` - Value []string `json:"value,omitempty"` - } - - // ObjectMatchValueRange represents an object match value resource for match criteria of type range - ObjectMatchValueRange struct { - Type ObjectMatchValueRangeType `json:"type"` - Value []int64 `json:"value,omitempty"` - } - - // Options represents an option resource for ObjectMatchValueObject - Options struct { - Value []string `json:"value,omitempty"` - ValueHasWildcard bool `json:"valueHasWildcard,omitempty"` - ValueCaseSensitive bool `json:"valueCaseSensitive,omitempty"` - ValueEscaped bool `json:"valueEscaped,omitempty"` - } - - //MatchRuleType enum type - MatchRuleType string - // MatchRuleFormat enum type - MatchRuleFormat string - // MatchOperator enum type - MatchOperator string - // CheckIPs enum type - CheckIPs string - // ObjectMatchValueRangeType enum type - ObjectMatchValueRangeType string - // ObjectMatchValueSimpleType enum type - ObjectMatchValueSimpleType string - // ObjectMatchValueObjectType enum type - ObjectMatchValueObjectType string -) - -const ( - // MatchRuleTypeALB represents rule type for ALB cloudlets - MatchRuleTypeALB MatchRuleType = "albMatchRule" - // MatchRuleTypeER represents rule type for ER cloudlets - MatchRuleTypeER MatchRuleType = "erMatchRule" -) - -const ( - // MatchRuleFormat10 represents default match rule format - MatchRuleFormat10 MatchRuleFormat = "1.0" - // MatchRuleFormatDefault represents default match rule format - MatchRuleFormatDefault = MatchRuleFormat10 -) - -const ( - // MatchOperatorContains represents contains operator - MatchOperatorContains MatchOperator = "contains" - // MatchOperatorExists represents exists operator - MatchOperatorExists MatchOperator = "exists" - // MatchOperatorEquals represents equals operator - MatchOperatorEquals MatchOperator = "equals" -) - -const ( - // CheckIPsConnectingIP represents connecting ip option - CheckIPsConnectingIP CheckIPs = "CONNECTING_IP" - // CheckIPsXFFHeaders represents xff headers option - CheckIPsXFFHeaders CheckIPs = "XFF_HEADERS" - // CheckIPsConnectingIPXFFHeaders represents connecting ip + xff headers option - CheckIPsConnectingIPXFFHeaders CheckIPs = "CONNECTING_IP XFF_HEADERS" -) - -const ( - // Range represents range option - Range ObjectMatchValueRangeType = "range" - // Simple represents simple option - Simple ObjectMatchValueSimpleType = "simple" - // Object represents object option - Object ObjectMatchValueObjectType = "object" ) // Validate validates ListPolicyVersionsRequest @@ -284,119 +133,6 @@ func (c CreatePolicyVersionRequest) Validate() error { return edgegriderr.ParseValidationErrors(errs) } -// Validate validates MatchRuleALB -func (m MatchRuleALB) Validate() error { - return validation.Errors{ - "Type": validation.Validate(m.Type, validation.In(MatchRuleTypeALB).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'albMatchRule' or '' (empty)", (&m).Type))), - "Name": validation.Validate(m.Name, validation.Length(0, 8192)), - "Start": validation.Validate(m.Start, validation.Min(0)), - "End": validation.Validate(m.End, validation.Min(0)), - "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), - "ForwardSettings.OriginID": validation.Validate(m.ForwardSettings.OriginID, validation.Required, validation.Length(0, 8192)), - "Matches": validation.Validate(m.Matches), - }.Filter() -} - -// Validate validates MatchRuleER -func (m MatchRuleER) Validate() error { - return validation.Errors{ - "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeER).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'erMatchRule' or '' (empty)", (&m).Type))), - "Name": validation.Validate(m.Name, validation.Length(0, 8192)), - "Start": validation.Validate(m.Start, validation.Min(0)), - "End": validation.Validate(m.End, validation.Min(0)), - "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), - "RedirectURL": validation.Validate(m.RedirectURL, validation.Required, validation.Length(1, 8192)), - "UseRelativeURL": validation.Validate(m.UseRelativeURL, validation.In("none", "copy_scheme_hostname", "relative_url").Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'none', 'copy_scheme_hostname', 'relative_url' or '' (empty)", (&m).UseRelativeURL))), - "StatusCode": validation.Validate(m.StatusCode, validation.Required, validation.In(301, 302, 303, 307, 308).Error( - fmt.Sprintf("value '%d' is invalid. Must be one of: 301, 302, 303, 307 or 308", (&m).StatusCode))), - "Matches": validation.Validate(m.Matches), - }.Filter() -} - -// Validate validates MatchCriteriaALB -func (m MatchCriteriaALB) Validate() error { - return validation.Errors{ - "MatchType": validation.Validate(m.MatchType, validation.In("clientip", "continent", "cookie", "countrycode", - "deviceCharacteristics", "extension", "header", "hostname", "method", "path", "protocol", "proxy", "query", "regioncode", "range").Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'clientip', 'continent', 'cookie', 'countrycode', 'deviceCharacteristics', "+ - "'extension', 'header', 'hostname', 'method', 'path', 'protocol', 'proxy', 'query', 'regioncode', 'range' or '' (empty)", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192), validation.Required.When(m.ObjectMatchValue == nil)), - "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), - "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueALBValidation)), - }.Filter() -} - -func objectMatchValueALBValidation(value interface{}) error { - if value == nil { - return nil - } - switch value.(type) { - case *ObjectMatchValueObject, *ObjectMatchValueSimple, *ObjectMatchValueRange: - return nil - default: - return fmt.Errorf("type %T is invalid. Must be one of: 'simple', 'range' or 'object'", value) - } -} - -// Validate validates MatchCriteriaER -func (m MatchCriteriaER) Validate() error { - return validation.Errors{ - "MatchType": validation.Validate(m.MatchType, validation.In("header", "hostname", "path", "extension", "query", - "regex", "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'regex', 'cookie', "+ - "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy' or '' (empty)", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), - "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), - "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueERValidation)), - }.Filter() -} - -func objectMatchValueERValidation(value interface{}) error { - if value == nil { - return nil - } - switch value.(type) { - case *ObjectMatchValueObject, *ObjectMatchValueSimple: - return nil - default: - return fmt.Errorf("type %T is invalid. Must be one of: 'simple' or 'object'", value) - } -} - -// Validate validates ObjectMatchValueRange -func (o ObjectMatchValueRange) Validate() error { - return validation.Errors{ - "Type": validation.Validate(o.Type, validation.In(Range).Error( - fmt.Sprintf("value '%s' is invalid. Must be: 'range'", (&o).Type))), - }.Filter() -} - -// Validate validates ObjectMatchValueSimple -func (o ObjectMatchValueSimple) Validate() error { - return validation.Errors{ - "Type": validation.Validate(o.Type, validation.In(Simple).Error( - fmt.Sprintf("value '%s' is invalid. Must be: 'simple'", (&o).Type))), - }.Filter() -} - -// Validate validates ObjectMatchValueObject -func (o ObjectMatchValueObject) Validate() error { - return validation.Errors{ - "Name": validation.Validate(o.Name, validation.Required, validation.Length(0, 8192)), - "Type": validation.Validate(o.Type, validation.Required, validation.In(Object).Error( - fmt.Sprintf("value '%s' is invalid. Must be: 'object'", (&o).Type))), - }.Filter() -} - // Validate validates UpdatePolicyVersionRequest func (o UpdatePolicyVersionRequest) Validate() error { errs := validation.Errors{ @@ -419,176 +155,8 @@ var ( ErrDeletePolicyVersion = errors.New("delete policy versions") // ErrUpdatePolicyVersion is returned when UpdatePolicyVersion fails ErrUpdatePolicyVersion = errors.New("update policy versions") - // ErrUnmarshallMatchCriteriaALB is returned when unmarshalling of MatchCriteriaALB fails - ErrUnmarshallMatchCriteriaALB = errors.New("unmarshalling MatchCriteriaALB") - // ErrUnmarshallMatchCriteriaER is returned when unmarshalling of MatchCriteriaER fails - ErrUnmarshallMatchCriteriaER = errors.New("unmarshalling MatchCriteriaER") - // ErrUnmarshallMatchRules is returned when unmarshalling of MatchRules fails - ErrUnmarshallMatchRules = errors.New("unmarshalling MatchRules") ) -func (m MatchRuleALB) cloudletType() string { - return "albMatchRule" -} - -func (m MatchRuleER) cloudletType() string { - return "erMatchRule" -} - -// matchRuleHandlers contains mapping between name of the type for MatchRule and its implementation -// It makes the UnmarshalJSON more compact and easier to support more cloudlet types -var matchRuleHandlers = map[string]func() MatchRule{ - "albMatchRule": func() MatchRule { return &MatchRuleALB{} }, - "erMatchRule": func() MatchRule { return &MatchRuleER{} }, -} - -// UnmarshalJSON helps to un-marshall items of MatchRules array as proper instances of *MatchRuleALB or *MatchRuleER -func (m *MatchRules) UnmarshalJSON(b []byte) error { - data := make([]map[string]interface{}, 0) - if err := json.Unmarshal(b, &data); err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchRules, err) - } - for _, matchRule := range data { - cloudletType, ok := matchRule["type"] - if !ok { - return fmt.Errorf("%w: match rule entry should contain 'type' field", ErrUnmarshallMatchRules) - } - cloudletTypeName, ok := cloudletType.(string) - if !ok { - return fmt.Errorf("%w: 'type' field on match rule entry should be a string", ErrUnmarshallMatchRules) - } - byteArr, err := json.Marshal(matchRule) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchRules, err) - } - - matchRuleType, ok := matchRuleHandlers[cloudletTypeName] - if !ok { - return fmt.Errorf("%w: unsupported match rule type: %s", ErrUnmarshallMatchRules, cloudletTypeName) - } - dst := matchRuleType() - err = json.Unmarshal(byteArr, dst) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchRules, err) - } - *m = append(*m, dst) - } - return nil -} - -// objectALBMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation -// It makes the UnmarshalJSON more compact and easier to support more types -var objectALBMatchValueHandlers = map[string]func() interface{}{ - "object": func() interface{} { return &ObjectMatchValueObject{} }, - "range": func() interface{} { return &ObjectMatchValueRange{} }, - "simple": func() interface{} { return &ObjectMatchValueSimple{} }, -} - -// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaALB as proper instance of *ObjectMatchValueObject, *ObjectMatchValueSimple or *ObjectMatchValueRange -func (m *MatchCriteriaALB) UnmarshalJSON(b []byte) error { - // matchCriteriaALB is an alias for MatchCriteriaALB for un-marshalling purposes - type matchCriteriaALB MatchCriteriaALB - - // populate common attributes using default json unmarshaler using aliased type - err := json.Unmarshal(b, (*matchCriteriaALB)(m)) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaALB, err) - } - if m.ObjectMatchValue == nil { - return nil - } - objectMatchValue, ok := m.ObjectMatchValue.(interface{}) - if !ok { - return fmt.Errorf("%w: objectMatchValue should be of type 'interface{}', but was '%T'", ErrUnmarshallMatchCriteriaALB, m.ObjectMatchValue) - } - - objectMatchValueMap, ok := objectMatchValue.(map[string]interface{}) - if !ok { - return fmt.Errorf("%w: structure of objectMatchValue should be 'map', but was '%T'", ErrUnmarshallMatchCriteriaALB, objectMatchValue) - } - objectMatchValueType, ok := objectMatchValueMap["type"] - if !ok { - return fmt.Errorf("%w: objectMatchValue should contain 'type' field", ErrUnmarshallMatchCriteriaALB) - } - objectMatchValueTypeName, ok := objectMatchValueType.(string) - if !ok { - return fmt.Errorf("%w: 'type' should be a string", ErrUnmarshallMatchCriteriaALB) - } - - createObjectMatchValue, ok := objectALBMatchValueHandlers[objectMatchValueTypeName] - if !ok { - return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaALB, objectMatchValueTypeName) - } - convertedObjectMatchValue := createObjectMatchValue() - marshal, err := json.Marshal(objectMatchValue) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaALB, err) - } - err = json.Unmarshal(marshal, convertedObjectMatchValue) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaALB, err) - } - m.ObjectMatchValue = convertedObjectMatchValue - - return nil -} - -// objectERMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation -// It makes the UnmarshalJSON more compact and easier to support more types -var objectERMatchValueHandlers = map[string]func() interface{}{ - "object": func() interface{} { return &ObjectMatchValueObject{} }, - "simple": func() interface{} { return &ObjectMatchValueSimple{} }, -} - -// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaER as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple -func (m *MatchCriteriaER) UnmarshalJSON(b []byte) error { - // matchCriteriaER is an alias for MatchCriteriaER for un-marshalling purposes - type matchCriteriaER MatchCriteriaER - - // populate common attributes using default json unmarshaler using aliased type - err := json.Unmarshal(b, (*matchCriteriaER)(m)) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaER, err) - } - if m.ObjectMatchValue == nil { - return nil - } - objectMatchValue, ok := m.ObjectMatchValue.(interface{}) - if !ok { - return fmt.Errorf("%w: objectMatchValue should be of type 'interface{}', but was '%T'", ErrUnmarshallMatchCriteriaER, m.ObjectMatchValue) - } - - objectMatchValueMap, ok := objectMatchValue.(map[string]interface{}) - if !ok { - return fmt.Errorf("%w: structure of objectMatchValue should be 'map', but was '%T'", ErrUnmarshallMatchCriteriaER, objectMatchValue) - } - objectMatchValueType, ok := objectMatchValueMap["type"] - if !ok { - return fmt.Errorf("%w: objectMatchValue should contain 'type' field", ErrUnmarshallMatchCriteriaER) - } - objectMatchValueTypeName, ok := objectMatchValueType.(string) - if !ok { - return fmt.Errorf("%w: 'type' should be a string", ErrUnmarshallMatchCriteriaER) - } - - createObjectMatchValue, ok := objectERMatchValueHandlers[objectMatchValueTypeName] - if !ok { - return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaER, objectMatchValueTypeName) - } - convertedObjectMatchValue := createObjectMatchValue() - marshal, err := json.Marshal(objectMatchValue) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaER, err) - } - err = json.Unmarshal(marshal, convertedObjectMatchValue) - if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaER, err) - } - m.ObjectMatchValue = convertedObjectMatchValue - - return nil -} - func (c *cloudlets) ListPolicyVersions(ctx context.Context, params ListPolicyVersionsRequest) ([]PolicyVersion, error) { logger := c.Log(ctx) logger.Debug("ListPolicyVersions") diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index fe60e88c..ae7ebc29 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -2,7 +2,6 @@ package cloudlets import ( "context" - "encoding/json" "errors" "net/http" "net/http/httptest" @@ -1802,6 +1801,185 @@ func TestCreatePolicyVersion(t *testing.T) { withError: ErrStructValidation, }, + "201 created, complex VP with objectMatchValue - simple": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleVP{ + Start: 0, + End: 0, + Type: "vpMatchRule", + Name: "rul3", + PassThroughPercent: -1, + ID: 0, + Matches: []MatchCriteriaVP{ + { + CaseSensitive: true, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + }, + }, + }, + PolicyID: 276858, + }, + responseStatus: http.StatusCreated, + responseBody: `{ + "activations": [], + "createDate": 1629981355165, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "vpMatchRule", + "end": 0, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": true, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "rul3", + "start": 0, + "useIncomingQueryString": false, + "passThroughPercent": -1 + } + ], + "policyId": 276858, + "revisionId": 4815968, + "rulesLocked": false, + "version": 6 +}`, + expectedPath: "/cloudlets/api/v2/policies/276858/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1629981355165, + CreatedBy: "jsmith", + Deleted: false, + Description: "", + LastModifiedBy: "jsmith", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", + MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, + MatchRules: MatchRules{ + &MatchRuleVP{ + Type: "vpMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Name: "rul3", + PassThroughPercent: -1, + Start: 0, + UseIncomingQueryString: false, + Matches: []MatchCriteriaVP{ + { + CaseSensitive: true, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + }, + }, + }, + }, + + "validation error, complex VP with unavailable objectMatchValue type - range": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleVP{ + Start: 0, + End: 0, + Type: "vpMatchRule", + PassThroughPercent: 50.50, + Name: "rul3", + ID: 0, + Matches: []MatchCriteriaVP{ + { + MatchOperator: "equals", + MatchType: "header", + Negate: false, + ObjectMatchValue: &ObjectMatchValueRange{ + Type: "range", + Value: []int64{1, 50}, + }, + }, + }, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, + + "validation error, simple VP missing passThrughPercent": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleVP{ + Start: 0, + End: 0, + Type: "vpMatchRule", + Name: "rul3", + ID: 0, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, + + "validation error, simple VP passThrughPercent out of range": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleVP{ + Start: 0, + End: 0, + Type: "vpMatchRule", + PassThroughPercent: 101, + Name: "rul3", + ID: 0, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, + "500 internal server error": { request: CreatePolicyVersionRequest{ PolicyID: 1, @@ -2217,292 +2395,3 @@ func TestUpdatePolicyVersion(t *testing.T) { }) } } - -func TestUnmarshalJSONMatchRules(t *testing.T) { - tests := map[string]struct { - withError error - responseBody string - expectedObject MatchRules - }{ - "valid MarchRuleALB": { - responseBody: ` - [ - { - "type": "albMatchRule", - "end": 0, - "forwardSettings": { - "originId": "alb_test_krk_dc1_only" - }, - "id": 0, - "matchURL": null, - "matches": [ - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "protocol", - "matchValue": "https", - "negate": false - }, - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "range", - "negate": false, - "objectMatchValue": { - "type": "range", - "value": [ - 1, - 50 - ] - } - }, - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "method", - "negate": false, - "objectMatchValue": { - "type": "simple", - "value": [ - "GET" - ] - } - } - ], - "name": "Rule3", - "start": 0 - } - ] -`, - expectedObject: MatchRules{ - &MatchRuleALB{ - Type: "albMatchRule", - End: 0, - ForwardSettings: ForwardSettings{ - OriginID: "alb_test_krk_dc1_only", - }, - ID: 0, - MatchURL: "", - Matches: []MatchCriteriaALB{ - { - CaseSensitive: false, - MatchOperator: "equals", - MatchType: "protocol", - MatchValue: "https", - Negate: false, - }, - { - CaseSensitive: false, - MatchOperator: "equals", - MatchType: "range", - Negate: false, - ObjectMatchValue: &ObjectMatchValueRange{ - Type: "range", - Value: []int64{1, 50}, - }, - }, - { - CaseSensitive: false, - MatchOperator: "equals", - MatchType: "method", - Negate: false, - ObjectMatchValue: &ObjectMatchValueSimple{ - Type: "simple", - Value: []string{"GET"}, - }, - }, - }, - Name: "Rule3", - Start: 0, - }, - }, - }, - - "invalid objectMatchValue type for ALB - range": { - withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: objectMatchValue has unexpected type: 'foo'"), - responseBody: ` - [ - { - "type": "albMatchRule", - "matches": [ - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "method", - "negate": false, - "objectMatchValue": { - "type": "foo", - "value": [ - "GET" - ] - } - } - ], - "name": "Rule3", - "start": 0 - } - ] -`, - }, - - "wrong type for object value type": { - withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: 'type' should be a string"), - responseBody: ` - [ - { - "type": "albMatchRule", - "matches": [ - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "method", - "negate": false, - "objectMatchValue": { - "type": 1, - "value": [ - "GET" - ] - } - } - ], - "name": "Rule3", - "start": 0 - } - ] -`, - }, - - "missing object value type": { - withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: objectMatchValue should contain 'type' field"), - responseBody: ` - [ - { - "type": "albMatchRule", - "matches": [ - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "method", - "negate": false, - "objectMatchValue": { - "value": [ - "GET" - ] - } - } - ], - "name": "Rule3", - "start": 0 - } - ] -`, - }, - - "invalid object value": { - withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaALB: structure of objectMatchValue should be 'map', but was 'string'"), - responseBody: ` - [ - { - "type": "albMatchRule", - "matches": [ - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "method", - "negate": false, - "objectMatchValue": "" - } - ], - "name": "Rule3", - "start": 0 - } - ] -`, - }, - - "invalid MatchRuleAP": { - responseBody: ` - [ - { - "type": "apMatchRule" - } - ] -`, - withError: errors.New("unmarshalling MatchRules: unsupported match rule type: apMatchRule"), - }, - - "invalid type": { - withError: errors.New("unmarshalling MatchRules: 'type' field on match rule entry should be a string"), - responseBody: ` - [ - { - "type": 1 - } - ] -`, - }, - - "invalid JSON": { - withError: errors.New("unexpected end of JSON input"), - responseBody: ` - [ - { - "type": "albMatchRule" - } - -`, - }, - - "missing type": { - withError: errors.New("unmarshalling MatchRules: match rule entry should contain 'type' field"), - responseBody: ` - [ - { - } - ] -`, - }, - - "invalid objectMatchValue type for ER - range": { - withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaER: objectMatchValue has unexpected type: 'range'"), - responseBody: ` - [ - { - "type": "erMatchRule", - "matches": [ - { - "caseSensitive": false, - "matchOperator": "equals", - "matchType": "method", - "negate": false, - "objectMatchValue": { - "type": "range", - "value": [ - 1, - 50 - ] - } - } - ], - "name": "Rule3", - "start": 0 - } - ] -`, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var matchRules MatchRules - err := json.Unmarshal([]byte(test.responseBody), &matchRules) - - if test.withError != nil { - assert.Equal(t, test.withError.Error(), err.Error()) - return - } - require.NoError(t, err) - assert.Equal(t, test.expectedObject, matchRules) - }) - } -} From 85a97463af8470379727a1b81b1c8a960a559b47 Mon Sep 17 00:00:00 2001 From: Mateusz Jakubiec Date: Thu, 2 Dec 2021 14:26:58 +0100 Subject: [PATCH 04/17] DXE-45 Add support for forward_rewrite (FR) cloudlet --- pkg/cloudlets/match_rule.go | 122 +++++- pkg/cloudlets/match_rule_test.go | 94 ++++- pkg/cloudlets/policy_version_test.go | 589 ++++++++++++++++++++++++++- 3 files changed, 778 insertions(+), 27 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 7e4f2b1a..0b38068e 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -29,12 +29,12 @@ type ( Matches []MatchCriteriaALB `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` MatchesAlways bool `json:"matchesAlways"` - ForwardSettings ForwardSettings `json:"forwardSettings"` + ForwardSettings ForwardSettingsALB `json:"forwardSettings"` Disabled bool `json:"disabled,omitempty"` } - // ForwardSettings represents forward settings - ForwardSettings struct { + // ForwardSettingsALB represents forward settings for ALB + ForwardSettingsALB struct { OriginID string `json:"originId"` } @@ -55,6 +55,26 @@ type ( Disabled bool `json:"disabled,omitempty"` } + // MatchRuleFR represents a match rule resource for create or update resource + MatchRuleFR struct { + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaFR `json:"matches,omitempty"` + MatchURL string `json:"matchURL,omitempty"` + ForwardSettings ForwardSettingsFR `json:"forwardSettings"` + Disabled bool `json:"disabled,omitempty"` + } + + // ForwardSettingsFR represents forward settings for FR + ForwardSettingsFR struct { + PathAndQS string `json:"pathAndQS,omitempty"` + UseIncomingQueryString bool `json:"useIncomingQueryString,omitempty"` + OriginID string `json:"originId,omitempty"` + } + // MatchRuleVP represents a match rule resource for create or update resource MatchRuleVP struct { Name string `json:"name,omitempty"` @@ -88,6 +108,10 @@ type ( // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple MatchCriteriaER MatchCriteria + // MatchCriteriaFR represents a match criteria resource for match rule for cloudlet FR + // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple + MatchCriteriaFR MatchCriteria + // MatchCriteriaVP represents a match criteria resource for match rule for cloudlet VP // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple MatchCriteriaVP MatchCriteria @@ -142,6 +166,8 @@ const ( MatchRuleTypeALB MatchRuleType = "albMatchRule" // MatchRuleTypeER represents rule type for ER cloudlets MatchRuleTypeER MatchRuleType = "erMatchRule" + // MatchRuleTypeFR represents rule type for FR cloudlets + MatchRuleTypeFR MatchRuleType = "frMatchRule" // MatchRuleTypeVP represents rule type for VP cloudlets MatchRuleTypeVP MatchRuleType = "vpMatchRule" ) @@ -183,8 +209,8 @@ const ( // Validate validates MatchRuleALB func (m MatchRuleALB) Validate() error { return validation.Errors{ - "Type": validation.Validate(m.Type, validation.In(MatchRuleTypeALB).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'albMatchRule' or '' (empty)", (&m).Type))), + "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeALB).Error( + fmt.Sprintf("value '%s' is invalid. Must be: 'albMatchRule'", (&m).Type))), "Name": validation.Validate(m.Name, validation.Length(0, 8192)), "Start": validation.Validate(m.Start, validation.Min(0)), "End": validation.Validate(m.End, validation.Min(0)), @@ -198,7 +224,7 @@ func (m MatchRuleALB) Validate() error { func (m MatchRuleER) Validate() error { return validation.Errors{ "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeER).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'erMatchRule' or '' (empty)", (&m).Type))), + fmt.Sprintf("value '%s' is invalid. Must be: 'erMatchRule'", (&m).Type))), "Name": validation.Validate(m.Name, validation.Length(0, 8192)), "Start": validation.Validate(m.Start, validation.Min(0)), "End": validation.Validate(m.End, validation.Min(0)), @@ -212,11 +238,27 @@ func (m MatchRuleER) Validate() error { }.Filter() } +// Validate validates MatchRuleFR +func (m MatchRuleFR) Validate() error { + return validation.Errors{ + "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeFR).Error( + fmt.Sprintf("value '%s' is invalid. Must be: 'frMatchRule'", (&m).Type))), + "Name": validation.Validate(m.Name, validation.Length(0, 8192)), + "Start": validation.Validate(m.Start, validation.Min(0)), + "End": validation.Validate(m.End, validation.Min(0)), + "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), + "Matches": validation.Validate(m.Matches), + "ForwardSettings": validation.Validate(m.ForwardSettings, validation.Required), + "ForwardSettings.PathAndQS": validation.Validate(m.ForwardSettings.PathAndQS, validation.Length(1, 8192)), + "ForwardSettings.OriginID": validation.Validate(m.ForwardSettings.OriginID, validation.Length(0, 8192)), + }.Filter() +} + // Validate validates MatchRuleVP func (m MatchRuleVP) Validate() error { return validation.Errors{ "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeVP).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'vpMatchRule' or '' (empty)", (&m).Type))), + fmt.Sprintf("value '%s' is invalid. Must be: 'vpMatchRule'", (&m).Type))), "Name": validation.Validate(m.Name, validation.Length(0, 8192)), "Start": validation.Validate(m.Start, validation.Min(0)), "End": validation.Validate(m.End, validation.Min(0)), @@ -258,6 +300,22 @@ func (m MatchCriteriaER) Validate() error { }.Filter() } +// Validate validates MatchCriteriaFR +func (m MatchCriteriaFR) Validate() error { + return validation.Errors{ + "MatchType": validation.Validate(m.MatchType, validation.Required, validation.In("header", "hostname", "path", "extension", "query", "regex", + "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'regex', 'cookie', "+ + "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), + "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + }.Filter() +} + // Validate validates MatchCriteriaVP func (m MatchCriteriaVP) Validate() error { return validation.Errors{ @@ -328,6 +386,8 @@ var ( ErrUnmarshallMatchCriteriaALB = errors.New("unmarshalling MatchCriteriaALB") // ErrUnmarshallMatchCriteriaER is returned when unmarshalling of MatchCriteriaER fails ErrUnmarshallMatchCriteriaER = errors.New("unmarshalling MatchCriteriaER") + // ErrUnmarshallMatchCriteriaFR is returned when unmarshalling of MatchCriteriaFR fails + ErrUnmarshallMatchCriteriaFR = errors.New("unmarshalling MatchCriteriaFR") // ErrUnmarshallMatchCriteriaVP is returned when unmarshalling of MatchCriteriaVP fails ErrUnmarshallMatchCriteriaVP = errors.New("unmarshalling MatchCriteriaVP") // ErrUnmarshallMatchRules is returned when unmarshalling of MatchRules fails @@ -342,6 +402,10 @@ func (m MatchRuleER) cloudletType() string { return "erMatchRule" } +func (m MatchRuleFR) cloudletType() string { + return "frMatchRule" +} + func (m MatchRuleVP) cloudletType() string { return "vpMatchRule" } @@ -351,6 +415,7 @@ func (m MatchRuleVP) cloudletType() string { var matchRuleHandlers = map[string]func() MatchRule{ "albMatchRule": func() MatchRule { return &MatchRuleALB{} }, "erMatchRule": func() MatchRule { return &MatchRuleER{} }, + "frMatchRule": func() MatchRule { return &MatchRuleFR{} }, "vpMatchRule": func() MatchRule { return &MatchRuleVP{} }, } @@ -467,6 +532,45 @@ func (m *MatchCriteriaER) UnmarshalJSON(b []byte) error { return nil } +// objectFRMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation +// It makes the UnmarshalJSON more compact and easier to support more types +var objectFRMatchValueHandlers = map[string]func() interface{}{ + "object": func() interface{} { return &ObjectMatchValueObject{} }, + "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +} + +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaFR as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple +func (m *MatchCriteriaFR) UnmarshalJSON(b []byte) error { + // matchCriteriaFR is an alias for MatchCriteriaFR for un-marshalling purposes + type matchCriteriaFR MatchCriteriaFR + + // populate common attributes using default json unmarshaler using aliased type + err := json.Unmarshal(b, (*matchCriteriaFR)(m)) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaFR, err) + } + if m.ObjectMatchValue == nil { + return nil + } + + objectMatchValueTypeName, err := getObjectMatchValueType(m.ObjectMatchValue) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaFR, err) + } + + createObjectMatchValue, ok := objectFRMatchValueHandlers[objectMatchValueTypeName] + if !ok { + return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaFR, objectMatchValueTypeName) + } + convertedObjectMatchValue, err := convertObjectMatchValue(m.ObjectMatchValue, createObjectMatchValue()) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaFR, err) + } + m.ObjectMatchValue = convertedObjectMatchValue + + return nil +} + // objectVPMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation // It makes the UnmarshalJSON more compact and easier to support more types var objectVPMatchValueHandlers = map[string]func() interface{}{ @@ -474,9 +578,9 @@ var objectVPMatchValueHandlers = map[string]func() interface{}{ "simple": func() interface{} { return &ObjectMatchValueSimple{} }, } -// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaER as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaVP as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple func (m *MatchCriteriaVP) UnmarshalJSON(b []byte) error { - // matchCriteriaER is an alias for MatchCriteriaER for un-marshalling purposes + // matchCriteriaVP is an alias for MatchCriteriaVP for un-marshalling purposes type matchCriteriaVP MatchCriteriaVP // populate common attributes using default json unmarshaler using aliased type diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index c590dfa0..2132bf02 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -69,7 +69,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 0, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_dc1_only", }, ID: 0, @@ -281,6 +281,98 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { ] `, }, + "invalid objectMatchValue type for FR - range": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaFR: objectMatchValue has unexpected type: 'range'"), + responseBody: ` + [ + { + "type": "frMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "range", + "value": [ + 1, + 50 + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + "valid MatchRuleFR": { + responseBody: ` + [ + { + "type": "frMatchRule", + "end": 0, + "id": 0, + "matchURL": null, + "forwardSettings": {}, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "protocol", + "matchValue": "https", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + expectedObject: MatchRules{ + &MatchRuleFR{ + Type: "frMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Matches: []MatchCriteriaFR{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "protocol", + MatchValue: "https", + Negate: false, + }, + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + Name: "Rule3", + Start: 0, + }, + }, + }, "invalid objectMatchValue type for VP - range": { withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaVP: objectMatchValue has unexpected type: 'range'"), diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index ae7ebc29..926c7ac2 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -471,7 +471,74 @@ func TestGetPolicyVersion(t *testing.T) { }, }, }, - + "200 OK, FR with disabled rule": { + request: GetPolicyVersionRequest{ + PolicyID: 276858, + Version: 6, + }, + responseStatus: http.StatusOK, + responseBody: `{ + "activations": [], + "createDate": 1629981355165, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", + "matchRuleFormat": "1.0", + "matchRules": [{ + "type": "frMatchRule", + "disabled": true, + "end": 0, + "id": 0, + "matchURL": null, + "forwardSettings": { + "pathAndQS": "/test_images/simpleimg.jpg", + "useIncomingQueryString": true, + "originId": "1234" + }, + "name": "rule 1", + "start": 0 + }], + "policyId": 276858, + "revisionId": 4815968, + "rulesLocked": false, + "version": 6 +}`, + expectedPath: "/cloudlets/api/v2/policies/276858/versions/6?omitRules=false", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1629981355165, + CreatedBy: "jsmith", + Deleted: false, + Description: "", + LastModifiedBy: "jsmith", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", + MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, + MatchRules: MatchRules{ + &MatchRuleFR{ + Name: "rule 1", + Type: "frMatchRule", + Start: 0, + End: 0, + ID: 0, + MatchURL: "", + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/simpleimg.jpg", + UseIncomingQueryString: true, + OriginID: "1234", + }, + Disabled: true, + }, + }, + }, + }, "500 internal server error": { request: GetPolicyVersionRequest{ PolicyID: 1, @@ -596,7 +663,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "albMatchRule", Name: "Rule3", - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_dc1_only", }, ID: 0, @@ -616,7 +683,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "albMatchRule", Name: "Rule1", - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_0_100", }, ID: 0, @@ -706,7 +773,7 @@ func TestCreatePolicyVersion(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 0, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_dc1_only", }, ID: 0, @@ -734,7 +801,7 @@ func TestCreatePolicyVersion(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 0, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_0_100", }, ID: 0, @@ -785,7 +852,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "albMatchRule", Name: "alb rule", - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_mutable", }, ID: 0, @@ -859,7 +926,7 @@ func TestCreatePolicyVersion(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 0, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_mutable", }, ID: 0, @@ -916,7 +983,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "albMatchRule", Name: "alb rule", - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_mutable", }, ID: 0, @@ -986,7 +1053,7 @@ func TestCreatePolicyVersion(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 0, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_mutable", }, ID: 0, @@ -1039,7 +1106,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "albMatchRule", Name: "alb rule", - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_mutable", }, ID: 0, @@ -1110,7 +1177,7 @@ func TestCreatePolicyVersion(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 0, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_mutable", }, ID: 0, @@ -1800,7 +1867,496 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, - + "201 created, complex FR": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleFR{ + Start: 0, + End: 0, + Type: "frMatchRule", + Name: "rul3", + ID: 0, + Matches: []MatchCriteriaFR{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + Negate: false, + }, + { + MatchType: "cookie", + MatchValue: "cookie=cookievalue", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + { + MatchType: "extension", + MatchValue: "txt", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + }, + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/simpleimg.jpg", + UseIncomingQueryString: true, + OriginID: "1234", + }, + }, + &MatchRuleFR{ + Name: "rule 1", + Type: "frMatchRule", + Start: 0, + End: 0, + ID: 0, + MatchURL: "ddd.aaa", + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/simpleimg.jpg", + UseIncomingQueryString: true, + OriginID: "1234", + }, + }, + &MatchRuleFR{ + Name: "rule 2", + Type: "frMatchRule", + Start: 0, + End: 0, + ID: 0, + MatchURL: "abc.com", + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/otherimage.jpg", + UseIncomingQueryString: true, + OriginID: "1234", + }, + }, + }, + }, + PolicyID: 276858, + }, + responseStatus: http.StatusCreated, + responseBody: `{ + "activations": [], + "createDate": 1629981355165, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "frMatchRule", + "akaRuleId": "893947a3d5a85c1b", + "end": 0, + "forwardSettings": { + "pathAndQS": "/test_images/otherimage.jpg", + "useIncomingQueryString": true, + "originId": "1234" + }, + "id": 0, + "location": "/cloudlets/api/v2/policies/276858/versions/1/rules/893947a3d5a85c1b", + "matchURL": null, + "matches": [ + { + "caseSensitive": true, + "matchOperator": "equals", + "matchType": "hostname", + "matchValue": "3333.dom", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "cookie", + "matchValue": "cookie=cookievalue", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "extension", + "matchValue": "txt", + "negate": false + } + ], + "name": "rul3", + "start": 0 + }, + { + "type": "frMatchRule", + "akaRuleId": "aa379d230efcded0", + "end": 0, + "forwardSettings": { + "pathAndQS": "/test_images/simpleimg.jpg", + "useIncomingQueryString": true, + "originId": "1234" + }, + "id": 0, + "location": "/cloudlets/api/v2/policies/276858/versions/1/rules/aa379d230efcded0", + "matchURL": "ddd.aaa", + "name": "rule 1", + "start": 0 + }, + { + "type": "frMatchRule", + "akaRuleId": "1afe03d843996766", + "end": 0, + "forwardSettings": { + "pathAndQS": "/test_images/otherimage.jpg", + "useIncomingQueryString": true, + "originId": "1234" + }, + "id": 0, + "location": "/cloudlets/api/v2/policies/276858/versions/1/rules/1afe03d843996766", + "matchURL": "abc.com", + "name": "rule 2", + "start": 0 + } + ], + "policyId": 276858, + "revisionId": 4815968, + "rulesLocked": false, + "version": 6 +}`, + expectedPath: "/cloudlets/api/v2/policies/276858/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1629981355165, + CreatedBy: "jsmith", + Deleted: false, + Description: "", + LastModifiedBy: "jsmith", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", + MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, + MatchRules: MatchRules{ + &MatchRuleFR{ + Type: "frMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Name: "rul3", + Start: 0, + Matches: []MatchCriteriaFR{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + Negate: false, + }, + { + MatchType: "cookie", + MatchValue: "cookie=cookievalue", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + { + MatchType: "extension", + MatchValue: "txt", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + }, + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/otherimage.jpg", + UseIncomingQueryString: true, + OriginID: "1234", + }, + }, + &MatchRuleFR{ + Name: "rule 1", + Type: "frMatchRule", + Start: 0, + End: 0, + ID: 0, + MatchURL: "ddd.aaa", + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/simpleimg.jpg", + UseIncomingQueryString: true, + OriginID: "1234", + }, + }, + &MatchRuleFR{ + Name: "rule 2", + Type: "frMatchRule", + Start: 0, + End: 0, + ID: 0, + MatchURL: "abc.com", + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/otherimage.jpg", + UseIncomingQueryString: true, + OriginID: "1234", + }, + }, + }, + }, + }, + "201 created, complex FR with objectMatchValue - object": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + Description: "New version 1630480693371", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleFR{ + ForwardSettings: ForwardSettingsFR{}, + Matches: []MatchCriteriaFR{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "header", + Negate: false, + ObjectMatchValue: &ObjectMatchValueObject{ + Type: "object", + Name: "Accept", + NameCaseSensitive: false, + NameHasWildcard: false, + Options: &Options{ + Value: []string{"asd", "qwe"}, + ValueHasWildcard: false, + ValueCaseSensitive: true, + ValueEscaped: false, + }, + }, + }, + }, + Start: 0, + End: 0, + Type: "frMatchRule", + Name: "rul3", + ID: 0, + }, + }, + }, + PolicyID: 139743, + }, + responseStatus: http.StatusCreated, + responseBody: ` +{ + "activations": [], + "createDate": 1630507099511, + "createdBy": "jsmith", + "deleted": false, + "description": "New version 1630480693371", + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1630507099511, + "location": "/cloudlets/api/v2/policies/139743/versions/798", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "frMatchRule", + "akaRuleId": "f2168e71692e6d9f", + "end": 0, + "forwardSettings": {}, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "header", + "negate": false, + "objectMatchValue": { + "type": "object", + "name": "Accept", + "options": { + "value": [ + "asd", + "qwe" + ], + "valueCaseSensitive": true + } + } + } + ], + "name": "rul3", + "start": 0 + } + ], + "policyId": 139743, + "revisionId": 4819450, + "rulesLocked": false, + "version": 798 +}`, + expectedPath: "/cloudlets/api/v2/policies/139743/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1630507099511, + CreatedBy: "jsmith", + Deleted: false, + Description: "New version 1630480693371", + LastModifiedBy: "jsmith", + LastModifiedDate: 1630507099511, + Location: "/cloudlets/api/v2/policies/139743/versions/798", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleFR{ + ForwardSettings: ForwardSettingsFR{}, + Matches: []MatchCriteriaFR{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "header", + Negate: false, + ObjectMatchValue: &ObjectMatchValueObject{ + Name: "Accept", + Type: "object", + NameCaseSensitive: false, + NameHasWildcard: false, + Options: &Options{ + Value: []string{"asd", "qwe"}, + ValueHasWildcard: false, + ValueCaseSensitive: true, + ValueEscaped: false, + }, + }, + }, + }, + Start: 0, + End: 0, + Type: "frMatchRule", + Name: "rul3", + ID: 0, + }, + }, + PolicyID: 139743, + RevisionID: 4819450, + RulesLocked: false, + Version: 798, + }, + }, + "201 created, complex FR with objectMatchValue - simple": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + Description: "New version 1630480693371", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleFR{ + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/otherimage.jpg", + UseIncomingQueryString: true, + }, + Matches: []MatchCriteriaFR{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + Start: 0, + End: 0, + Type: "frMatchRule", + Name: "rul3", + ID: 0, + }, + }, + }, + PolicyID: 139743, + }, + responseStatus: http.StatusCreated, + responseBody: ` +{ + "activations": [], + "createDate": 1630507099511, + "createdBy": "jsmith", + "deleted": false, + "description": "New version 1630480693371", + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1630507099511, + "location": "/cloudlets/api/v2/policies/139743/versions/798", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "frMatchRule", + "akaRuleId": "f2168e71692e6d9f", + "end": 0, + "forwardSettings": { + "pathAndQS": "/test_images/otherimage.jpg", + "useIncomingQueryString": true + }, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "rul3", + "start": 0 + } + ], + "policyId": 139743, + "revisionId": 4819450, + "rulesLocked": false, + "version": 798 +}`, + expectedPath: "/cloudlets/api/v2/policies/139743/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1630507099511, + CreatedBy: "jsmith", + Deleted: false, + Description: "New version 1630480693371", + LastModifiedBy: "jsmith", + LastModifiedDate: 1630507099511, + Location: "/cloudlets/api/v2/policies/139743/versions/798", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleFR{ + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "/test_images/otherimage.jpg", + UseIncomingQueryString: true, + }, + Matches: []MatchCriteriaFR{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + Start: 0, + End: 0, + Type: "frMatchRule", + Name: "rul3", + ID: 0, + }, + }, + PolicyID: 139743, + RevisionID: 4819450, + RulesLocked: false, + Version: 798, + }, + }, "201 created, complex VP with objectMatchValue - simple": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1979,7 +2535,6 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, - "500 internal server error": { request: CreatePolicyVersionRequest{ PolicyID: 1, @@ -2166,7 +2721,7 @@ func TestUpdatePolicyVersion(t *testing.T) { End: 2, Type: "albMatchRule", Name: "Rule3", - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_dc1_only", }, ID: 0, @@ -2185,7 +2740,7 @@ func TestUpdatePolicyVersion(t *testing.T) { End: 0, Type: "albMatchRule", Name: "Rule1", - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_0_100", }, ID: 0, @@ -2284,7 +2839,7 @@ func TestUpdatePolicyVersion(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 2, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_dc1_only", }, ID: 10, @@ -2304,7 +2859,7 @@ func TestUpdatePolicyVersion(t *testing.T) { &MatchRuleALB{ Type: "albMatchRule", End: 0, - ForwardSettings: ForwardSettings{ + ForwardSettings: ForwardSettingsALB{ OriginID: "alb_test_krk_0_100", }, ID: 0, From 3f6f01262a79c831b7adeb1c94a296bcdd7a27dd Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Mon, 6 Dec 2021 12:21:20 +0000 Subject: [PATCH 05/17] DXE-44 Edgegrid - support for phased_release cloudlets (Continuous Deployment (CD) is known as Phased Release (PR) in production) Merge in DEVEXP/akamaiopen-edgegrid-golang from feature/DXE-44-edgegrid-support-for-phased_release-cloudlets-continuous-deployment-cd-is-known-as to feature/sp-cloudlets2 --- pkg/cloudlets/match_rule.go | 107 +++- pkg/cloudlets/match_rule_test.go | 103 ++++ pkg/cloudlets/policy_version_test.go | 860 +++++++++++++++++++++++---- 3 files changed, 967 insertions(+), 103 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 0b38068e..06e8e3d9 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -38,6 +38,26 @@ type ( OriginID string `json:"originId"` } + // MatchRuleCD represents a match rule resource for create or update resource + MatchRuleCD struct { + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaCD `json:"matches,omitempty"` + MatchURL string `json:"matchURL,omitempty"` + ForwardSettings ForwardSettingsCD `json:"forwardSettings"` + Disabled bool `json:"disabled,omitempty"` + MatchesAlways bool `json:"matchesAlways"` + } + + // ForwardSettingsCD represents forward settings for CD aka PR + ForwardSettingsCD struct { + OriginID string `json:"originId"` + Percent int `json:"percent"` + } + // MatchRuleER represents a match rule resource for create or update resource MatchRuleER struct { Name string `json:"name,omitempty"` @@ -104,6 +124,10 @@ type ( // ObjectMatchValue can contain ObjectMatchValueObject, ObjectMatchValueSimple or ObjectMatchValueRange MatchCriteriaALB MatchCriteria + // MatchCriteriaCD represents a match criteria resource for match rule for cloudlet CD aka PR + // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple + MatchCriteriaCD MatchCriteria + // MatchCriteriaER represents a match criteria resource for match rule for cloudlet ER // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple MatchCriteriaER MatchCriteria @@ -164,6 +188,8 @@ type ( const ( // MatchRuleTypeALB represents rule type for ALB cloudlets MatchRuleTypeALB MatchRuleType = "albMatchRule" + // MatchRuleTypeCD represents rule type for CD aka PR cloudlets + MatchRuleTypeCD MatchRuleType = "cdMatchRule" // MatchRuleTypeER represents rule type for ER cloudlets MatchRuleTypeER MatchRuleType = "erMatchRule" // MatchRuleTypeFR represents rule type for FR cloudlets @@ -175,8 +201,6 @@ const ( const ( // MatchRuleFormat10 represents default match rule format MatchRuleFormat10 MatchRuleFormat = "1.0" - // MatchRuleFormatDefault represents default match rule format - MatchRuleFormatDefault = MatchRuleFormat10 ) const ( @@ -220,6 +244,22 @@ func (m MatchRuleALB) Validate() error { }.Filter() } +// Validate validates MatchRuleCD +func (m MatchRuleCD) Validate() error { + return validation.Errors{ + "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeCD).Error( + fmt.Sprintf("value '%s' is invalid. Must be: 'cdMatchRule'", (&m).Type))), + "Name": validation.Validate(m.Name, validation.Length(0, 8192)), + "Start": validation.Validate(m.Start, validation.Min(0)), + "End": validation.Validate(m.End, validation.Min(0)), + "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), + "ForwardSettings": validation.Validate(m.ForwardSettings, validation.Required), + "ForwardSettings.OriginID": validation.Validate(m.ForwardSettings.OriginID, validation.Required, validation.Length(0, 8192)), + "ForwardSettings.Percent": validation.Validate(m.ForwardSettings.Percent, validation.Required, validation.Min(1), validation.Max(100)), + "Matches": validation.Validate(m.Matches), + }.Filter() +} + // Validate validates MatchRuleER func (m MatchRuleER) Validate() error { return validation.Errors{ @@ -284,6 +324,23 @@ func (m MatchCriteriaALB) Validate() error { }.Filter() } +// Validate validates MatchCriteriaCD +func (m MatchCriteriaCD) Validate() error { + return validation.Errors{ + "MatchType": validation.Validate(m.MatchType, validation.In("header", "hostname", "path", "extension", + "query", "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", + "method", "proxy").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'cookie', "+ + "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), + "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + }.Filter() +} + // Validate validates MatchCriteriaER func (m MatchCriteriaER) Validate() error { return validation.Errors{ @@ -384,6 +441,8 @@ func (o ObjectMatchValueObject) Validate() error { var ( // ErrUnmarshallMatchCriteriaALB is returned when unmarshalling of MatchCriteriaALB fails ErrUnmarshallMatchCriteriaALB = errors.New("unmarshalling MatchCriteriaALB") + // ErrUnmarshallMatchCriteriaCD is returned when unmarshalling of MatchCriteriaCD fails + ErrUnmarshallMatchCriteriaCD = errors.New("unmarshalling MatchCriteriaCD") // ErrUnmarshallMatchCriteriaER is returned when unmarshalling of MatchCriteriaER fails ErrUnmarshallMatchCriteriaER = errors.New("unmarshalling MatchCriteriaER") // ErrUnmarshallMatchCriteriaFR is returned when unmarshalling of MatchCriteriaFR fails @@ -398,6 +457,10 @@ func (m MatchRuleALB) cloudletType() string { return "albMatchRule" } +func (m MatchRuleCD) cloudletType() string { + return "cdMatchRule" +} + func (m MatchRuleER) cloudletType() string { return "erMatchRule" } @@ -414,6 +477,7 @@ func (m MatchRuleVP) cloudletType() string { // It makes the UnmarshalJSON more compact and easier to support more cloudlet types var matchRuleHandlers = map[string]func() MatchRule{ "albMatchRule": func() MatchRule { return &MatchRuleALB{} }, + "cdMatchRule": func() MatchRule { return &MatchRuleCD{} }, "erMatchRule": func() MatchRule { return &MatchRuleER{} }, "frMatchRule": func() MatchRule { return &MatchRuleFR{} }, "vpMatchRule": func() MatchRule { return &MatchRuleVP{} }, @@ -493,6 +557,45 @@ func (m *MatchCriteriaALB) UnmarshalJSON(b []byte) error { return nil } +// objectCDMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation +// It makes the UnmarshalJSON more compact and easier to support more types +var objectCDMatchValueHandlers = map[string]func() interface{}{ + "object": func() interface{} { return &ObjectMatchValueObject{} }, + "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +} + +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaCD as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple +func (m *MatchCriteriaCD) UnmarshalJSON(b []byte) error { + // matchCriteriaCD is an alias for MatchCriteriaCD for un-marshalling purposes + type matchCriteriaCD MatchCriteriaCD + + // populate common attributes using default json unmarshaler using aliased type + err := json.Unmarshal(b, (*matchCriteriaCD)(m)) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaCD, err) + } + if m.ObjectMatchValue == nil { + return nil + } + + objectMatchValueTypeName, err := getObjectMatchValueType(m.ObjectMatchValue) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaCD, err) + } + + createObjectMatchValue, ok := objectCDMatchValueHandlers[objectMatchValueTypeName] + if !ok { + return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaCD, objectMatchValueTypeName) + } + convertedObjectMatchValue, err := convertObjectMatchValue(m.ObjectMatchValue, createObjectMatchValue()) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaCD, err) + } + m.ObjectMatchValue = convertedObjectMatchValue + + return nil +} + // objectERMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation // It makes the UnmarshalJSON more compact and easier to support more types var objectERMatchValueHandlers = map[string]func() interface{}{ diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index 2132bf02..ad70909e 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -254,6 +254,34 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { `, }, + "invalid objectMatchValue type for CD - range": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaCD: objectMatchValue has unexpected type: 'range'"), + responseBody: ` + [ + { + "type": "cdMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "range", + "value": [ + 1, + 50 + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, + "invalid objectMatchValue type for ER - range": { withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaER: objectMatchValue has unexpected type: 'range'"), responseBody: ` @@ -281,6 +309,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { ] `, }, + "invalid objectMatchValue type for FR - range": { withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaFR: objectMatchValue has unexpected type: 'range'"), responseBody: ` @@ -308,6 +337,80 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { ] `, }, + + "valid MatchRuleCD": { + responseBody: ` + [ + { + "type": "cdMatchRule", + "end": 0, + "id": 0, + "matchURL": null, + "forwardSettings": { + "originId": "fr_test_krk_dc2", + "percent": 62 + }, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "protocol", + "matchValue": "https", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + expectedObject: MatchRules{ + &MatchRuleCD{ + Type: "cdMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Matches: []MatchCriteriaCD{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "protocol", + MatchValue: "https", + Negate: false, + }, + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + Name: "Rule3", + Start: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "fr_test_krk_dc2", + Percent: 62, + }, + }, + }, + }, + "valid MatchRuleFR": { responseBody: ` [ diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index 926c7ac2..0eafa291 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -343,6 +343,99 @@ func TestGetPolicyVersion(t *testing.T) { }, }, }, + "200 OK, CD rule": { + request: GetPolicyVersionRequest{ + PolicyID: 276858, + Version: 6, + }, + responseStatus: http.StatusOK, + responseBody: `{ + "activations": [], + "createDate": 1638547203265, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1638547203265, + "location": "/cloudlets/api/v2/policies/325401/versions/3", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "cdMatchRule", + "akaRuleId": "b151ca68e51f5a61", + "end": 0, + "forwardSettings": { + "originId": "fr_test_krk_dc2", + "percent": 11 + }, + "id": 0, + "location": "/cloudlets/api/v2/policies/325401/versions/3/rules/b151ca68e51f5a61", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "rule 1", + "start": 0 + } + ], + "policyId": 325401, + "revisionId": 4888857, + "rulesLocked": false, + "version": 3 +}`, + expectedPath: "/cloudlets/api/v2/policies/276858/versions/6?omitRules=false", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1638547203265, + CreatedBy: "jsmith", + Deleted: false, + Description: "", + LastModifiedBy: "jsmith", + LastModifiedDate: 1638547203265, + Location: "/cloudlets/api/v2/policies/325401/versions/3", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleCD{ + Type: "cdMatchRule", + End: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "fr_test_krk_dc2", + Percent: 11, + }, + ID: 0, + Matches: []MatchCriteriaCD{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{ + "GET"}, + }, + }, + }, + Name: "rule 1", + Start: 0, + }, + }, + PolicyID: 325401, + RevisionID: 4888857, + RulesLocked: false, + Version: 3, + }, + }, "200 OK, ER rule with disabled=false": { request: GetPolicyVersionRequest{ PolicyID: 276858, @@ -1036,31 +1129,497 @@ func TestCreatePolicyVersion(t *testing.T) { "policyId": 139743, "revisionId": 4819449, "rulesLocked": false, - "version": 797 + "version": 797 +}`, + expectedPath: "/cloudlets/api/v2/policies/139743/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1630506044027, + CreatedBy: "jsmith", + Deleted: false, + Description: "New version 1630480693371", + LastModifiedBy: "jsmith", + LastModifiedDate: 1630506044027, + Location: "/cloudlets/api/v2/policies/139743/versions/797", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleALB{ + Type: "albMatchRule", + End: 0, + ForwardSettings: ForwardSettingsALB{ + OriginID: "alb_test_krk_mutable", + }, + ID: 0, + MatchURL: "", + Matches: []MatchCriteriaALB{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + MatchesAlways: true, + Name: "alb rule", + Start: 0, + }, + }, + PolicyID: 139743, + RevisionID: 4819449, + RulesLocked: false, + Version: 797, + }, + }, + + "201 created, complex ALB with objectMatchValue - range": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + + Description: "New version 1630480693371", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleALB{ + Matches: []MatchCriteriaALB{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "range", + Negate: false, + ObjectMatchValue: &ObjectMatchValueRange{ + Type: "range", + Value: []int64{1, 50}, + }, + }, + }, + Start: 0, + End: 0, + Type: "albMatchRule", + Name: "alb rule", + ForwardSettings: ForwardSettingsALB{ + OriginID: "alb_test_krk_mutable", + }, + ID: 0, + MatchesAlways: true, + }, + }, + }, + PolicyID: 139743, + }, + responseStatus: http.StatusCreated, + responseBody: ` +{ + "activations": [], + "createDate": 1630507099511, + "createdBy": "jsmith", + "deleted": false, + "description": "New version 1630480693371", + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1630507099511, + "location": "/cloudlets/api/v2/policies/139743/versions/798", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "albMatchRule", + "end": 0, + "forwardSettings": { + "originId": "alb_test_krk_mutable" + }, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "range", + "negate": false, + "objectMatchValue": { + "type": "range", + "value": [ + 1, + 50 + ] + } + } + ], + "matchesAlways": true, + "name": "alb rule", + "start": 0 + } + ], + "policyId": 139743, + "revisionId": 4819450, + "rulesLocked": false, + "version": 798 +}`, + expectedPath: "/cloudlets/api/v2/policies/139743/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1630507099511, + CreatedBy: "jsmith", + Deleted: false, + Description: "New version 1630480693371", + LastModifiedBy: "jsmith", + LastModifiedDate: 1630507099511, + Location: "/cloudlets/api/v2/policies/139743/versions/798", + MatchRuleFormat: "1.0", + MatchRules: MatchRules{ + &MatchRuleALB{ + Type: "albMatchRule", + End: 0, + ForwardSettings: ForwardSettingsALB{ + OriginID: "alb_test_krk_mutable", + }, + ID: 0, + MatchURL: "", + Matches: []MatchCriteriaALB{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "range", + Negate: false, + ObjectMatchValue: &ObjectMatchValueRange{ + Type: "range", + Value: []int64{1, 50}, + }, + }, + }, + MatchesAlways: true, + Name: "alb rule", + Start: 0, + }, + }, + PolicyID: 139743, + RevisionID: 4819450, + RulesLocked: false, + Version: 798, + }, + }, + + "201 created, complex CD": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleCD{ + Start: 0, + End: 0, + Type: "cdMatchRule", + Name: "rul3", + ID: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + Negate: false, + }, + { + MatchType: "cookie", + MatchValue: "cookie=cookievalue", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + { + MatchType: "extension", + MatchValue: "txt", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + }, + }, + &MatchRuleCD{ + Start: 0, + End: 0, + Type: "cdMatchRule", + Name: "rule 2", + MatchURL: "ddd.aaa", + ID: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + }, + &MatchRuleCD{ + Type: "cdMatchRule", + ID: 0, + Name: "r1", + Start: 0, + End: 0, + MatchURL: "abc.com", + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + }, + }, + }, + PolicyID: 276858, + }, + responseStatus: http.StatusCreated, + responseBody: `{ + "activations": [], + "createDate": 1629981355165, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "cdMatchRule", + "end": 0, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": true, + "matchOperator": "equals", + "matchType": "hostname", + "matchValue": "3333.dom", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "cookie", + "matchValue": "cookie=cookievalue", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "extension", + "matchValue": "txt", + "negate": false + } + ], + "name": "rul3", + "redirectURL": "/abc/sss", + "start": 0, + "forwardSettings": { + "originId": "some_origin", + "percent": 10 + } + }, + { + "type": "cdMatchRule", + "end": 0, + "id": 0, + "matchURL": "ddd.aaa", + "name": "rule 2", + "redirectURL": "sss.com", + "start": 0, + "statusCode": 301, + "useIncomingQueryString": true, + "useRelativeUrl": "none" + }, + { + "type": "cdMatchRule", + "end": 0, + "id": 0, + "matchURL": "abc.com", + "name": "r1", + "redirectURL": "/ddd", + "start": 0, + "statusCode": 301, + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true, + "useRelativeUrl": "copy_scheme_hostname" + } + ], + "policyId": 276858, + "revisionId": 4815968, + "rulesLocked": false, + "version": 6 +}`, + expectedPath: "/cloudlets/api/v2/policies/276858/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1629981355165, + CreatedBy: "jsmith", + Deleted: false, + Description: "", + LastModifiedBy: "jsmith", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", + MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, + MatchRules: MatchRules{ + &MatchRuleCD{ + Type: "cdMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Name: "rul3", + Start: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + Negate: false, + }, + { + MatchType: "cookie", + MatchValue: "cookie=cookievalue", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + { + MatchType: "extension", + MatchValue: "txt", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + }, + }, + &MatchRuleCD{ + Type: "cdMatchRule", + End: 0, + ID: 0, + MatchURL: "ddd.aaa", + Name: "rule 2", + Start: 0, + }, + &MatchRuleCD{ + Type: "cdMatchRule", + End: 0, + ID: 0, + MatchURL: "abc.com", + Name: "r1", + Start: 0, + }, + }, + }, + }, + "201 created, complex CD with objectMatchValue - simple": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleCD{ + Start: 0, + End: 0, + Type: "cdMatchRule", + Name: "rul3", + ID: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ + { + CaseSensitive: true, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + }, + }, + }, + PolicyID: 276858, + }, + responseStatus: http.StatusCreated, + responseBody: `{ + "activations": [], + "createDate": 1629981355165, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "cdMatchRule", + "end": 0, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": true, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "rul3", + "redirectURL": "/abc/sss", + "start": 0, + "forwardSettings": { + "originId": "some_origin", + "percent": 10 + } + } + ], + "policyId": 276858, + "revisionId": 4815968, + "rulesLocked": false, + "version": 6 }`, - expectedPath: "/cloudlets/api/v2/policies/139743/versions", + expectedPath: "/cloudlets/api/v2/policies/276858/versions", expectedResponse: &PolicyVersion{ Activations: []PolicyActivation{}, - CreateDate: 1630506044027, + CreateDate: 1629981355165, CreatedBy: "jsmith", Deleted: false, - Description: "New version 1630480693371", + Description: "", LastModifiedBy: "jsmith", - LastModifiedDate: 1630506044027, - Location: "/cloudlets/api/v2/policies/139743/versions/797", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, MatchRules: MatchRules{ - &MatchRuleALB{ - Type: "albMatchRule", - End: 0, - ForwardSettings: ForwardSettingsALB{ - OriginID: "alb_test_krk_mutable", - }, + &MatchRuleCD{ + Type: "cdMatchRule", + End: 0, ID: 0, MatchURL: "", - Matches: []MatchCriteriaALB{ + Name: "rul3", + Start: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ { - CaseSensitive: false, + CaseSensitive: true, MatchOperator: "equals", MatchType: "method", Negate: false, @@ -1070,141 +1629,244 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - MatchesAlways: true, - Name: "alb rule", - Start: 0, }, }, - PolicyID: 139743, - RevisionID: 4819449, - RulesLocked: false, - Version: 797, }, }, - - "201 created, complex ALB with objectMatchValue - range": { + "201 created, complex CD with objectMatchValue - object": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ - - Description: "New version 1630480693371", - MatchRuleFormat: "1.0", MatchRules: MatchRules{ - &MatchRuleALB{ - Matches: []MatchCriteriaALB{ + &MatchRuleCD{ + Start: 0, + End: 0, + Type: "cdMatchRule", + Name: "rul3", + ID: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ { - CaseSensitive: false, MatchOperator: "equals", - MatchType: "range", + MatchType: "header", Negate: false, - ObjectMatchValue: &ObjectMatchValueRange{ - Type: "range", - Value: []int64{1, 50}, + ObjectMatchValue: &ObjectMatchValueObject{ + Type: "object", + Name: "CD", + Options: &Options{ + Value: []string{ + "text/html*", + "text/css*", + "application/x-javascript*", + }, + ValueHasWildcard: true, + }, }, }, }, - Start: 0, - End: 0, - Type: "albMatchRule", - Name: "alb rule", - ForwardSettings: ForwardSettingsALB{ - OriginID: "alb_test_krk_mutable", - }, - ID: 0, - MatchesAlways: true, }, }, }, - PolicyID: 139743, + PolicyID: 276858, }, responseStatus: http.StatusCreated, - responseBody: ` -{ + responseBody: `{ "activations": [], - "createDate": 1630507099511, + "createDate": 1629981355165, "createdBy": "jsmith", "deleted": false, - "description": "New version 1630480693371", + "description": null, "lastModifiedBy": "jsmith", - "lastModifiedDate": 1630507099511, - "location": "/cloudlets/api/v2/policies/139743/versions/798", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", "matchRuleFormat": "1.0", "matchRules": [ { - "type": "albMatchRule", + "type": "cdMatchRule", "end": 0, - "forwardSettings": { - "originId": "alb_test_krk_mutable" - }, "id": 0, "matchURL": null, "matches": [ { - "caseSensitive": false, "matchOperator": "equals", - "matchType": "range", + "matchType": "hostname", "negate": false, - "objectMatchValue": { - "type": "range", - "value": [ - 1, - 50 - ] - } + "objectMatchValue": { + "type": "object", + "name": "CD", + "options": { + "value": [ + "text/html*", + "text/css*", + "application/x-javascript*" + ], + "valueHasWildcard": true + } + } } ], - "matchesAlways": true, - "name": "alb rule", - "start": 0 + "name": "rul3", + "redirectURL": "/abc/sss", + "start": 0, + "forwardSettings": { + "originId": "some_origin", + "percent": 10 + } } ], - "policyId": 139743, - "revisionId": 4819450, + "policyId": 276858, + "revisionId": 4815968, "rulesLocked": false, - "version": 798 + "version": 6 }`, - expectedPath: "/cloudlets/api/v2/policies/139743/versions", + expectedPath: "/cloudlets/api/v2/policies/276858/versions", expectedResponse: &PolicyVersion{ Activations: []PolicyActivation{}, - CreateDate: 1630507099511, + CreateDate: 1629981355165, CreatedBy: "jsmith", Deleted: false, - Description: "New version 1630480693371", + Description: "", LastModifiedBy: "jsmith", - LastModifiedDate: 1630507099511, - Location: "/cloudlets/api/v2/policies/139743/versions/798", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, MatchRules: MatchRules{ - &MatchRuleALB{ - Type: "albMatchRule", - End: 0, - ForwardSettings: ForwardSettingsALB{ - OriginID: "alb_test_krk_mutable", - }, + &MatchRuleCD{ + Type: "cdMatchRule", + End: 0, ID: 0, MatchURL: "", - Matches: []MatchCriteriaALB{ + Name: "rul3", + Start: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ { - CaseSensitive: false, MatchOperator: "equals", - MatchType: "range", + MatchType: "hostname", Negate: false, - ObjectMatchValue: &ObjectMatchValueRange{ - Type: "range", - Value: []int64{1, 50}, + ObjectMatchValue: &ObjectMatchValueObject{ + Type: "object", + Name: "CD", + Options: &Options{ + Value: []string{ + "text/html*", + "text/css*", + "application/x-javascript*", + }, + ValueHasWildcard: true, + }, }, }, }, - MatchesAlways: true, - Name: "alb rule", - Start: 0, }, }, - PolicyID: 139743, - RevisionID: 4819450, - RulesLocked: false, - Version: 798, }, }, + "validation error, complex CD with unavailable objectMatchValue type - range": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleCD{ + Start: 0, + End: 0, + Type: "cdMatchRule", + Name: "rul3", + ID: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ + { + MatchOperator: "equals", + MatchType: "header", + Negate: false, + ObjectMatchValue: &ObjectMatchValueRange{ + Type: "range", + Value: []int64{1, 50}, + }, + }, + }, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, + "validation error, complex CD missing forwardSettings": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleCD{ + Start: 0, + End: 0, + Type: "cdMatchRule", + Name: "rul3", + ID: 0, + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + Matches: []MatchCriteriaCD{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + Negate: false, + }, + { + MatchType: "cookie", + MatchValue: "cookie=cookievalue", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + { + MatchType: "extension", + MatchValue: "txt", + MatchOperator: "equals", + Negate: false, + CaseSensitive: false, + }, + }, + }, + &MatchRuleCD{ + Start: 0, + End: 0, + Type: "cdMatchRule", + Name: "rule 2", + MatchURL: "ddd.aaa", + ID: 0, + }, + &MatchRuleCD{ + Type: "cdMatchRule", + ID: 0, + Name: "r1", + Start: 0, + End: 0, + MatchURL: "abc.com", + ForwardSettings: ForwardSettingsCD{ + OriginID: "some_origin", + Percent: 10, + }, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, "201 created, complex ER": { request: CreatePolicyVersionRequest{ @@ -1431,7 +2093,6 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "201 created, complex ER with objectMatchValue - simple": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1551,7 +2212,6 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "201 created, complex ER with objectMatchValue - object": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1690,7 +2350,6 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "201 created, ER with empty/no useRelativeURL": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1835,7 +2494,6 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "validation error, complex ER with unavailable objectMatchValue type - range": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1867,6 +2525,7 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, + "201 created, complex FR": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -2357,6 +3016,7 @@ func TestCreatePolicyVersion(t *testing.T) { Version: 798, }, }, + "201 created, complex VP with objectMatchValue - simple": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -2468,7 +3128,6 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "validation error, complex VP with unavailable objectMatchValue type - range": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -2498,8 +3157,7 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, - - "validation error, simple VP missing passThrughPercent": { + "validation error, simple VP missing passThroughPercent": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ @@ -2516,8 +3174,7 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, - - "validation error, simple VP passThrughPercent out of range": { + "validation error, simple VP passThroughPercent out of range": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ @@ -2535,6 +3192,7 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, + "500 internal server error": { request: CreatePolicyVersionRequest{ PolicyID: 1, From 177d27eb3ac92b89239407d1bb3096969c64f81c Mon Sep 17 00:00:00 2001 From: Piyush Kaushik Date: Mon, 6 Dec 2021 14:07:14 +0000 Subject: [PATCH 06/17] DXE-42 Edgegrid support for cloudlet Api Prioritization AP Merge in DEVEXP/akamaiopen-edgegrid-golang from feature/DXE-42-edgegrid-support-for-cloudlet-api_prioritization-ap to feature/sp-cloudlets2 --- pkg/cloudlets/match_rule.go | 197 +++++++++++------ pkg/cloudlets/match_rule_test.go | 99 ++++++++- pkg/cloudlets/policy_version_test.go | 306 +++++++++++++++++++++++++++ 3 files changed, 535 insertions(+), 67 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 06e8e3d9..618ac63b 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -38,6 +38,20 @@ type ( OriginID string `json:"originId"` } + // MatchRuleAP represents an API Prioritization match rule resource for create or update + MatchRuleAP struct { + Disabled bool `json:"disabled,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaAP `json:"matches,omitempty"` + MatchURL string `json:"matchURL,omitempty"` + Name string `json:"name,omitempty"` + PassThroughPercent float64 `json:"passThroughPercent"` + Start int `json:"start,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + UseIncomingQueryString bool `json:"useIncomingQueryString,omitempty"` + } + // MatchRuleCD represents a match rule resource for create or update resource MatchRuleCD struct { Name string `json:"name,omitempty"` @@ -124,6 +138,10 @@ type ( // ObjectMatchValue can contain ObjectMatchValueObject, ObjectMatchValueSimple or ObjectMatchValueRange MatchCriteriaALB MatchCriteria + // MatchCriteriaAP represents a match criteria resource for match rule for cloudlet AP + // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple + MatchCriteriaAP MatchCriteria + // MatchCriteriaCD represents a match criteria resource for match rule for cloudlet CD aka PR // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple MatchCriteriaCD MatchCriteria @@ -188,6 +206,8 @@ type ( const ( // MatchRuleTypeALB represents rule type for ALB cloudlets MatchRuleTypeALB MatchRuleType = "albMatchRule" + // MatchRuleTypeAP represents rule type for AP cloudlets + MatchRuleTypeAP MatchRuleType = "apMatchRule" // MatchRuleTypeCD represents rule type for CD aka PR cloudlets MatchRuleTypeCD MatchRuleType = "cdMatchRule" // MatchRuleTypeER represents rule type for ER cloudlets @@ -230,6 +250,49 @@ const ( Object ObjectMatchValueObjectType = "object" ) +var ( + // ErrUnmarshallMatchCriteriaALB is returned when unmarshalling of MatchCriteriaALB fails + ErrUnmarshallMatchCriteriaALB = errors.New("unmarshalling MatchCriteriaALB") + // ErrUnmarshallMatchCriteriaAP is returned when unmarshalling of MatchCriteriaAP fails + ErrUnmarshallMatchCriteriaAP = errors.New("unmarshalling MatchCriteriaAP") + // ErrUnmarshallMatchCriteriaCD is returned when unmarshalling of MatchCriteriaCD fails + ErrUnmarshallMatchCriteriaCD = errors.New("unmarshalling MatchCriteriaCD") + // ErrUnmarshallMatchCriteriaER is returned when unmarshalling of MatchCriteriaER fails + ErrUnmarshallMatchCriteriaER = errors.New("unmarshalling MatchCriteriaER") + // ErrUnmarshallMatchCriteriaFR is returned when unmarshalling of MatchCriteriaFR fails + ErrUnmarshallMatchCriteriaFR = errors.New("unmarshalling MatchCriteriaFR") + // ErrUnmarshallMatchCriteriaVP is returned when unmarshalling of MatchCriteriaVP fails + ErrUnmarshallMatchCriteriaVP = errors.New("unmarshalling MatchCriteriaVP") + // ErrUnmarshallMatchRules is returned when unmarshalling of MatchRules fails + ErrUnmarshallMatchRules = errors.New("unmarshalling MatchRules") +) + +// matchRuleHandlers contains mapping between name of the type for MatchRule and its implementation +// It makes the UnmarshalJSON more compact and easier to support more cloudlet types +var matchRuleHandlers = map[string]func() MatchRule{ + "albMatchRule": func() MatchRule { return &MatchRuleALB{} }, + "apMatchRule": func() MatchRule { return &MatchRuleAP{} }, + "cdMatchRule": func() MatchRule { return &MatchRuleCD{} }, + "erMatchRule": func() MatchRule { return &MatchRuleER{} }, + "vpMatchRule": func() MatchRule { return &MatchRuleVP{} }, + "frMatchRule": func() MatchRule { return &MatchRuleFR{} }, +} + +// objectALBMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation +// It makes the UnmarshalJSON more compact and easier to support more types +var objectALBMatchValueHandlers = map[string]func() interface{}{ + "object": func() interface{} { return &ObjectMatchValueObject{} }, + "range": func() interface{} { return &ObjectMatchValueRange{} }, + "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +} + +// simpleObjectMatchValueHandlers contains mapping between name of the types (simple or object) for ObjectMatchValue and their implementations +// It makes the UnmarshalJSON more compact and easier to support more types +var simpleObjectMatchValueHandlers = map[string]func() interface{}{ + "object": func() interface{} { return &ObjectMatchValueObject{} }, + "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +} + // Validate validates MatchRuleALB func (m MatchRuleALB) Validate() error { return validation.Errors{ @@ -244,6 +307,20 @@ func (m MatchRuleALB) Validate() error { }.Filter() } +// Validate validates MatchRuleAP +func (m MatchRuleAP) Validate() error { + return validation.Errors{ + "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeAP).Error( + fmt.Sprintf("value '%s' is invalid. Must be: 'apMatchRule'", (&m).Type))), + "Name": validation.Validate(m.Name, validation.Length(0, 8192)), + "Start": validation.Validate(m.Start, validation.Min(0)), + "End": validation.Validate(m.End, validation.Min(0)), + "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), + "PassThroughPercent": validation.Validate(m.PassThroughPercent, validation.Required, validation.Min(-1.0), validation.Max(100.0)), + "Matches": validation.Validate(m.Matches), + }.Filter() +} + // Validate validates MatchRuleCD func (m MatchRuleCD) Validate() error { return validation.Errors{ @@ -324,6 +401,23 @@ func (m MatchCriteriaALB) Validate() error { }.Filter() } +// Validate validates MatchCriteriaAP +func (m MatchCriteriaAP) Validate() error { + return validation.Errors{ + "MatchType": validation.Validate(m.MatchType, validation.In( + "header", "hostname", "path", "extension", "query", "cookie", "deviceCharacteristics", "clientip", + "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'cookie', "+ + "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), + "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + }.Filter() +} + // Validate validates MatchCriteriaCD func (m MatchCriteriaCD) Validate() error { return validation.Errors{ @@ -379,7 +473,7 @@ func (m MatchCriteriaVP) Validate() error { "MatchType": validation.Validate(m.MatchType, validation.In("header", "hostname", "path", "extension", "query", "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'cookie', "+ - "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy' or '' (empty)", (&m).MatchType))), + "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), @@ -438,25 +532,14 @@ func (o ObjectMatchValueObject) Validate() error { }.Filter() } -var ( - // ErrUnmarshallMatchCriteriaALB is returned when unmarshalling of MatchCriteriaALB fails - ErrUnmarshallMatchCriteriaALB = errors.New("unmarshalling MatchCriteriaALB") - // ErrUnmarshallMatchCriteriaCD is returned when unmarshalling of MatchCriteriaCD fails - ErrUnmarshallMatchCriteriaCD = errors.New("unmarshalling MatchCriteriaCD") - // ErrUnmarshallMatchCriteriaER is returned when unmarshalling of MatchCriteriaER fails - ErrUnmarshallMatchCriteriaER = errors.New("unmarshalling MatchCriteriaER") - // ErrUnmarshallMatchCriteriaFR is returned when unmarshalling of MatchCriteriaFR fails - ErrUnmarshallMatchCriteriaFR = errors.New("unmarshalling MatchCriteriaFR") - // ErrUnmarshallMatchCriteriaVP is returned when unmarshalling of MatchCriteriaVP fails - ErrUnmarshallMatchCriteriaVP = errors.New("unmarshalling MatchCriteriaVP") - // ErrUnmarshallMatchRules is returned when unmarshalling of MatchRules fails - ErrUnmarshallMatchRules = errors.New("unmarshalling MatchRules") -) - func (m MatchRuleALB) cloudletType() string { return "albMatchRule" } +func (m MatchRuleAP) cloudletType() string { + return "apMatchRule" +} + func (m MatchRuleCD) cloudletType() string { return "cdMatchRule" } @@ -473,16 +556,6 @@ func (m MatchRuleVP) cloudletType() string { return "vpMatchRule" } -// matchRuleHandlers contains mapping between name of the type for MatchRule and its implementation -// It makes the UnmarshalJSON more compact and easier to support more cloudlet types -var matchRuleHandlers = map[string]func() MatchRule{ - "albMatchRule": func() MatchRule { return &MatchRuleALB{} }, - "cdMatchRule": func() MatchRule { return &MatchRuleCD{} }, - "erMatchRule": func() MatchRule { return &MatchRuleER{} }, - "frMatchRule": func() MatchRule { return &MatchRuleFR{} }, - "vpMatchRule": func() MatchRule { return &MatchRuleVP{} }, -} - // UnmarshalJSON helps to un-marshall items of MatchRules array as proper instances of *MatchRuleALB or *MatchRuleER func (m *MatchRules) UnmarshalJSON(b []byte) error { data := make([]map[string]interface{}, 0) @@ -517,14 +590,6 @@ func (m *MatchRules) UnmarshalJSON(b []byte) error { return nil } -// objectALBMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation -// It makes the UnmarshalJSON more compact and easier to support more types -var objectALBMatchValueHandlers = map[string]func() interface{}{ - "object": func() interface{} { return &ObjectMatchValueObject{} }, - "range": func() interface{} { return &ObjectMatchValueRange{} }, - "simple": func() interface{} { return &ObjectMatchValueSimple{} }, -} - // UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaALB as proper instance of *ObjectMatchValueObject, *ObjectMatchValueSimple or *ObjectMatchValueRange func (m *MatchCriteriaALB) UnmarshalJSON(b []byte) error { // matchCriteriaALB is an alias for MatchCriteriaALB for un-marshalling purposes @@ -557,11 +622,36 @@ func (m *MatchCriteriaALB) UnmarshalJSON(b []byte) error { return nil } -// objectCDMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation -// It makes the UnmarshalJSON more compact and easier to support more types -var objectCDMatchValueHandlers = map[string]func() interface{}{ - "object": func() interface{} { return &ObjectMatchValueObject{} }, - "simple": func() interface{} { return &ObjectMatchValueSimple{} }, +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaAP as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple +func (m *MatchCriteriaAP) UnmarshalJSON(b []byte) error { + // matchCriteriaER is an alias for MatchCriteriaER for un-marshalling purposes + type matchCriteriaAP MatchCriteriaAP + + // populate common attributes using default json unmarshaler using aliased type + err := json.Unmarshal(b, (*matchCriteriaAP)(m)) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaAP, err) + } + if m.ObjectMatchValue == nil { + return nil + } + + objectMatchValueTypeName, err := getObjectMatchValueType(m.ObjectMatchValue) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaAP, err) + } + + createObjectMatchValue, ok := simpleObjectMatchValueHandlers[objectMatchValueTypeName] + if !ok { + return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaAP, objectMatchValueTypeName) + } + convertedObjectMatchValue, err := convertObjectMatchValue(m.ObjectMatchValue, createObjectMatchValue()) + if err != nil { + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaAP, err) + } + m.ObjectMatchValue = convertedObjectMatchValue + + return nil } // UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaCD as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple @@ -583,7 +673,7 @@ func (m *MatchCriteriaCD) UnmarshalJSON(b []byte) error { return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaCD, err) } - createObjectMatchValue, ok := objectCDMatchValueHandlers[objectMatchValueTypeName] + createObjectMatchValue, ok := simpleObjectMatchValueHandlers[objectMatchValueTypeName] if !ok { return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaCD, objectMatchValueTypeName) } @@ -596,13 +686,6 @@ func (m *MatchCriteriaCD) UnmarshalJSON(b []byte) error { return nil } -// objectERMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation -// It makes the UnmarshalJSON more compact and easier to support more types -var objectERMatchValueHandlers = map[string]func() interface{}{ - "object": func() interface{} { return &ObjectMatchValueObject{} }, - "simple": func() interface{} { return &ObjectMatchValueSimple{} }, -} - // UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaER as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple func (m *MatchCriteriaER) UnmarshalJSON(b []byte) error { // matchCriteriaER is an alias for MatchCriteriaER for un-marshalling purposes @@ -622,7 +705,7 @@ func (m *MatchCriteriaER) UnmarshalJSON(b []byte) error { return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaER, err) } - createObjectMatchValue, ok := objectERMatchValueHandlers[objectMatchValueTypeName] + createObjectMatchValue, ok := simpleObjectMatchValueHandlers[objectMatchValueTypeName] if !ok { return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaER, objectMatchValueTypeName) } @@ -635,13 +718,6 @@ func (m *MatchCriteriaER) UnmarshalJSON(b []byte) error { return nil } -// objectFRMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation -// It makes the UnmarshalJSON more compact and easier to support more types -var objectFRMatchValueHandlers = map[string]func() interface{}{ - "object": func() interface{} { return &ObjectMatchValueObject{} }, - "simple": func() interface{} { return &ObjectMatchValueSimple{} }, -} - // UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaFR as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple func (m *MatchCriteriaFR) UnmarshalJSON(b []byte) error { // matchCriteriaFR is an alias for MatchCriteriaFR for un-marshalling purposes @@ -661,7 +737,7 @@ func (m *MatchCriteriaFR) UnmarshalJSON(b []byte) error { return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaFR, err) } - createObjectMatchValue, ok := objectFRMatchValueHandlers[objectMatchValueTypeName] + createObjectMatchValue, ok := simpleObjectMatchValueHandlers[objectMatchValueTypeName] if !ok { return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaFR, objectMatchValueTypeName) } @@ -674,13 +750,6 @@ func (m *MatchCriteriaFR) UnmarshalJSON(b []byte) error { return nil } -// objectVPMatchValueHandlers contains mapping between name of the type for ObjectMatchValue and its implementation -// It makes the UnmarshalJSON more compact and easier to support more types -var objectVPMatchValueHandlers = map[string]func() interface{}{ - "object": func() interface{} { return &ObjectMatchValueObject{} }, - "simple": func() interface{} { return &ObjectMatchValueSimple{} }, -} - // UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaVP as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple func (m *MatchCriteriaVP) UnmarshalJSON(b []byte) error { // matchCriteriaVP is an alias for MatchCriteriaVP for un-marshalling purposes @@ -700,7 +769,7 @@ func (m *MatchCriteriaVP) UnmarshalJSON(b []byte) error { return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaVP, err) } - createObjectMatchValue, ok := objectVPMatchValueHandlers[objectMatchValueTypeName] + createObjectMatchValue, ok := simpleObjectMatchValueHandlers[objectMatchValueTypeName] if !ok { return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaVP, objectMatchValueTypeName) } diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index ad70909e..e1d33980 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -211,15 +211,15 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { `, }, - "invalid MatchRuleAP": { + "invalid MatchRuleXX": { responseBody: ` [ { - "type": "apMatchRule" + "type": "xxMatchRule" } ] `, - withError: errors.New("unmarshalling MatchRules: unsupported match rule type: apMatchRule"), + withError: errors.New("unmarshalling MatchRules: unsupported match rule type: xxMatchRule"), }, "invalid type": { @@ -504,6 +504,33 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { ] `, }, + "invalid objectMatchValue type for AP - range": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaAP: objectMatchValue has unexpected type: 'range'"), + responseBody: ` + [ + { + "type": "apMatchRule", + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "range", + "value": [ + 1, + 50 + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + }, "valid MatchRuleVP": { responseBody: ` @@ -571,6 +598,72 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { }, }, }, + "valid MatchRuleAP": { + responseBody: ` + [ + { + "type": "apMatchRule", + "end": 0, + "passThroughPercent": 50.50, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "protocol", + "matchValue": "https", + "negate": false + }, + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "Rule3", + "start": 0 + } + ] +`, + expectedObject: MatchRules{ + &MatchRuleAP{ + Type: "apMatchRule", + End: 0, + PassThroughPercent: 50.50, + ID: 0, + MatchURL: "", + Matches: []MatchCriteriaAP{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "protocol", + MatchValue: "https", + Negate: false, + }, + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + Name: "Rule3", + Start: 0, + }, + }, + }, } for name, test := range tests { diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index 0eafa291..b12fc41d 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -3128,6 +3128,243 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, + + "201 created, complex AP with objectMatchValue - simple": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleAP{ + Start: 0, + End: 0, + Type: "apMatchRule", + Name: "rul3", + PassThroughPercent: -1, + ID: 0, + Matches: []MatchCriteriaAP{ + { + CaseSensitive: true, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + }, + }, + }, + PolicyID: 276858, + }, + responseStatus: http.StatusCreated, + responseBody: `{ + "activations": [], + "createDate": 1629981355165, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "apMatchRule", + "end": 0, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": true, + "matchOperator": "equals", + "matchType": "method", + "negate": false, + "objectMatchValue": { + "type": "simple", + "value": [ + "GET" + ] + } + } + ], + "name": "rul3", + "start": 0, + "useIncomingQueryString": false, + "passThroughPercent": -1 + } + ], + "policyId": 276858, + "revisionId": 4815968, + "rulesLocked": false, + "version": 6 +}`, + expectedPath: "/cloudlets/api/v2/policies/276858/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1629981355165, + CreatedBy: "jsmith", + Deleted: false, + Description: "", + LastModifiedBy: "jsmith", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", + MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, + MatchRules: MatchRules{ + &MatchRuleAP{ + Type: "apMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Name: "rul3", + PassThroughPercent: -1, + Start: 0, + UseIncomingQueryString: false, + Matches: []MatchCriteriaAP{ + { + CaseSensitive: true, + MatchOperator: "equals", + MatchType: "method", + Negate: false, + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{"GET"}, + }, + }, + }, + }, + }, + }, + }, + + "201 created, complex AP with objectMatchValue - object": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleAP{ + Start: 0, + End: 0, + Type: "apMatchRule", + Name: "rul3", + PassThroughPercent: -1, + ID: 0, + UseIncomingQueryString: false, + Matches: []MatchCriteriaAP{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "header", + Negate: false, + ObjectMatchValue: &ObjectMatchValueObject{ + Type: "object", + Name: "AP", + Options: &Options{ + Value: []string{"y"}, + ValueHasWildcard: true, + }, + }, + }, + }, + }, + }, + }, + PolicyID: 276858, + }, + responseStatus: http.StatusCreated, + responseBody: `{ + "activations": [], + "createDate": 1629981355165, + "createdBy": "jsmith", + "deleted": false, + "description": null, + "lastModifiedBy": "jsmith", + "lastModifiedDate": 1629981355165, + "location": "/cloudlets/api/v2/policies/276858/versions/6", + "matchRuleFormat": "1.0", + "matchRules": [ + { + "type": "apMatchRule", + "end": 0, + "id": 0, + "matchURL": null, + "matches": [ + { + "caseSensitive": false, + "matchOperator": "equals", + "matchType": "header", + "negate": false, + "objectMatchValue": { + "type": "object", + "name": "AP", + "options": { + "value": [ + "y" + ], + "valueHasWildcard": true + } + } + } + ], + "name": "rul3", + "start": 0, + "useIncomingQueryString": false, + "passThroughPercent": -1 + } + ], + "policyId": 276858, + "revisionId": 4815968, + "rulesLocked": false, + "version": 6 +}`, + expectedPath: "/cloudlets/api/v2/policies/276858/versions", + expectedResponse: &PolicyVersion{ + Activations: []PolicyActivation{}, + CreateDate: 1629981355165, + CreatedBy: "jsmith", + Deleted: false, + Description: "", + LastModifiedBy: "jsmith", + LastModifiedDate: 1629981355165, + Location: "/cloudlets/api/v2/policies/276858/versions/6", + MatchRuleFormat: "1.0", + PolicyID: 276858, + RevisionID: 4815968, + RulesLocked: false, + Version: 6, + MatchRules: MatchRules{ + &MatchRuleAP{ + Type: "apMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Name: "rul3", + PassThroughPercent: -1, + Start: 0, + Matches: []MatchCriteriaAP{ + { + CaseSensitive: false, + MatchOperator: "equals", + MatchType: "header", + Negate: false, + ObjectMatchValue: &ObjectMatchValueObject{ + Type: "object", + Name: "AP", + Options: &Options{ + Value: []string{"y"}, + ValueHasWildcard: true, + }, + }, + }, + }, + }, + }, + }, + }, + "validation error, complex VP with unavailable objectMatchValue type - range": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -3157,6 +3394,37 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, + + "validation error, complex AP with unavailable objectMatchValue type - range": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleAP{ + Start: 0, + End: 0, + Type: "apMatchRule", + PassThroughPercent: 50.50, + Name: "rul3", + ID: 0, + Matches: []MatchCriteriaAP{ + { + MatchOperator: "equals", + MatchType: "header", + Negate: false, + ObjectMatchValue: &ObjectMatchValueRange{ + Type: "range", + Value: []int64{1, 50}, + }, + }, + }, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, + "validation error, simple VP missing passThroughPercent": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -3174,6 +3442,25 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, + + "validation error, simple AP missing passThrughPercent": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleAP{ + Start: 0, + End: 0, + Type: "apMatchRule", + Name: "rul3", + ID: 0, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, + "validation error, simple VP passThroughPercent out of range": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -3193,6 +3480,25 @@ func TestCreatePolicyVersion(t *testing.T) { withError: ErrStructValidation, }, + "validation error, simple AP passThroughPercent out of range": { + request: CreatePolicyVersionRequest{ + CreatePolicyVersion: CreatePolicyVersion{ + MatchRules: MatchRules{ + &MatchRuleAP{ + Start: 0, + End: 0, + Type: "apMatchRule", + PassThroughPercent: 101, + Name: "rul3", + ID: 0, + }, + }, + }, + PolicyID: 276858, + }, + withError: ErrStructValidation, + }, + "500 internal server error": { request: CreatePolicyVersionRequest{ PolicyID: 1, From 39bd206e9a0d744aba865d0e6b7e4e5828184850 Mon Sep 17 00:00:00 2001 From: Piyush Kaushik Date: Tue, 7 Dec 2021 11:54:57 +0000 Subject: [PATCH 07/17] DXE-6 fixed edgegrid order of AP match rule Merge in DEVEXP/akamaiopen-edgegrid-golang from feature/DXE-6-terraform-provider-data-source-akamai_cloudlets_api_prioritization_match_rule to feature/sp-cloudlets2 --- pkg/cloudlets/match_rule.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 618ac63b..f9601db4 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -40,16 +40,16 @@ type ( // MatchRuleAP represents an API Prioritization match rule resource for create or update MatchRuleAP struct { - Disabled bool `json:"disabled,omitempty"` + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` End int `json:"end,omitempty"` ID int64 `json:"id,omitempty"` Matches []MatchCriteriaAP `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` - Name string `json:"name,omitempty"` - PassThroughPercent float64 `json:"passThroughPercent"` - Start int `json:"start,omitempty"` - Type MatchRuleType `json:"type,omitempty"` UseIncomingQueryString bool `json:"useIncomingQueryString,omitempty"` + PassThroughPercent float64 `json:"passThroughPercent"` + Disabled bool `json:"disabled,omitempty"` } // MatchRuleCD represents a match rule resource for create or update resource From 0ad0992d7828268ae270c6225d1e76e3a06d47ac Mon Sep 17 00:00:00 2001 From: Darek Stopka Date: Tue, 7 Dec 2021 18:24:46 +0100 Subject: [PATCH 08/17] DXE-152 Fix problems with useIncomingQueryString and passThroughPercent - useIncomingQueryString is not used b the API for AP and VP cloudlets - passThroughPercent validation did not pass when value was 0 due to being marked as required --- pkg/cloudlets/match_rule.go | 59 +++++++++++++++++----------- pkg/cloudlets/match_rule_test.go | 6 ++- pkg/cloudlets/policy_version_test.go | 59 +++++++++++++--------------- pkg/cloudlets/tools/ptr.go | 5 +++ 4 files changed, 73 insertions(+), 56 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index f9601db4..0595425e 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -40,16 +40,15 @@ type ( // MatchRuleAP represents an API Prioritization match rule resource for create or update MatchRuleAP struct { - Name string `json:"name,omitempty"` - Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` - ID int64 `json:"id,omitempty"` - Matches []MatchCriteriaAP `json:"matches,omitempty"` - MatchURL string `json:"matchURL,omitempty"` - UseIncomingQueryString bool `json:"useIncomingQueryString,omitempty"` - PassThroughPercent float64 `json:"passThroughPercent"` - Disabled bool `json:"disabled,omitempty"` + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaAP `json:"matches,omitempty"` + MatchURL string `json:"matchURL,omitempty"` + PassThroughPercent *float64 `json:"passThroughPercent"` + Disabled bool `json:"disabled,omitempty"` } // MatchRuleCD represents a match rule resource for create or update resource @@ -111,16 +110,15 @@ type ( // MatchRuleVP represents a match rule resource for create or update resource MatchRuleVP struct { - Name string `json:"name,omitempty"` - Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` - ID int64 `json:"id,omitempty"` - Matches []MatchCriteriaVP `json:"matches,omitempty"` - MatchURL string `json:"matchURL,omitempty"` - UseIncomingQueryString bool `json:"useIncomingQueryString,omitempty"` - PassThroughPercent float64 `json:"passThroughPercent"` - Disabled bool `json:"disabled,omitempty"` + Name string `json:"name,omitempty"` + Type MatchRuleType `json:"type,omitempty"` + Start int `json:"start,omitempty"` + End int `json:"end,omitempty"` + ID int64 `json:"id,omitempty"` + Matches []MatchCriteriaVP `json:"matches,omitempty"` + MatchURL string `json:"matchURL,omitempty"` + PassThroughPercent *float64 `json:"passThroughPercent"` + Disabled bool `json:"disabled,omitempty"` } // MatchCriteria represents a match criteria resource for match rule for cloudlet @@ -316,7 +314,7 @@ func (m MatchRuleAP) Validate() error { "Start": validation.Validate(m.Start, validation.Min(0)), "End": validation.Validate(m.End, validation.Min(0)), "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), - "PassThroughPercent": validation.Validate(m.PassThroughPercent, validation.Required, validation.Min(-1.0), validation.Max(100.0)), + "PassThroughPercent": validation.Validate(m.PassThroughPercent, validation.By(passThroughPercentValidation)), "Matches": validation.Validate(m.Matches), }.Filter() } @@ -380,7 +378,7 @@ func (m MatchRuleVP) Validate() error { "Start": validation.Validate(m.Start, validation.Min(0)), "End": validation.Validate(m.End, validation.Min(0)), "MatchURL": validation.Validate(m.MatchURL, validation.Length(0, 8192)), - "PassThroughPercent": validation.Validate(m.PassThroughPercent, validation.Required, validation.Min(-1.0), validation.Max(100.0)), + "PassThroughPercent": validation.Validate(m.PassThroughPercent, validation.By(passThroughPercentValidation)), "Matches": validation.Validate(m.Matches), }.Filter() } @@ -507,6 +505,23 @@ func objectMatchValueSimpleOrRangeOrObjectValidation(value interface{}) error { } } +func passThroughPercentValidation(value interface{}) error { + v, ok := value.(*float64) + if !ok { + return fmt.Errorf("type %T is invalid. Must be *float64", value) + } + if v == nil { + return fmt.Errorf("cannot be blank") + } + if *v < -1 { + return fmt.Errorf("must be no less than -1") + } + if *v > 100 { + return fmt.Errorf("must be no greater than 100") + } + return nil +} + // Validate validates ObjectMatchValueRange func (o ObjectMatchValueRange) Validate() error { return validation.Errors{ diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index e1d33980..62ae4940 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/require" "github.com/tj/assert" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/cloudlets/tools" ) func TestUnmarshalJSONMatchRules(t *testing.T) { @@ -571,7 +573,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { &MatchRuleVP{ Type: "vpMatchRule", End: 0, - PassThroughPercent: 50.50, + PassThroughPercent: tools.Float64Ptr(50.50), ID: 0, MatchURL: "", Matches: []MatchCriteriaVP{ @@ -637,7 +639,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { &MatchRuleAP{ Type: "apMatchRule", End: 0, - PassThroughPercent: 50.50, + PassThroughPercent: tools.Float64Ptr(50.50), ID: 0, MatchURL: "", Matches: []MatchCriteriaAP{ diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index b12fc41d..dce9ca68 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -3026,7 +3026,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "vpMatchRule", Name: "rul3", - PassThroughPercent: -1, + PassThroughPercent: tools.Float64Ptr(-1), ID: 0, Matches: []MatchCriteriaVP{ { @@ -3078,7 +3078,6 @@ func TestCreatePolicyVersion(t *testing.T) { ], "name": "rul3", "start": 0, - "useIncomingQueryString": false, "passThroughPercent": -1 } ], @@ -3104,14 +3103,13 @@ func TestCreatePolicyVersion(t *testing.T) { Version: 6, MatchRules: MatchRules{ &MatchRuleVP{ - Type: "vpMatchRule", - End: 0, - ID: 0, - MatchURL: "", - Name: "rul3", - PassThroughPercent: -1, - Start: 0, - UseIncomingQueryString: false, + Type: "vpMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Name: "rul3", + PassThroughPercent: tools.Float64Ptr(-1), + Start: 0, Matches: []MatchCriteriaVP{ { CaseSensitive: true, @@ -3138,7 +3136,7 @@ func TestCreatePolicyVersion(t *testing.T) { End: 0, Type: "apMatchRule", Name: "rul3", - PassThroughPercent: -1, + PassThroughPercent: tools.Float64Ptr(0), ID: 0, Matches: []MatchCriteriaAP{ { @@ -3216,14 +3214,13 @@ func TestCreatePolicyVersion(t *testing.T) { Version: 6, MatchRules: MatchRules{ &MatchRuleAP{ - Type: "apMatchRule", - End: 0, - ID: 0, - MatchURL: "", - Name: "rul3", - PassThroughPercent: -1, - Start: 0, - UseIncomingQueryString: false, + Type: "apMatchRule", + End: 0, + ID: 0, + MatchURL: "", + Name: "rul3", + PassThroughPercent: tools.Float64Ptr(-1), + Start: 0, Matches: []MatchCriteriaAP{ { CaseSensitive: true, @@ -3246,13 +3243,12 @@ func TestCreatePolicyVersion(t *testing.T) { CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ &MatchRuleAP{ - Start: 0, - End: 0, - Type: "apMatchRule", - Name: "rul3", - PassThroughPercent: -1, - ID: 0, - UseIncomingQueryString: false, + Start: 0, + End: 0, + Type: "apMatchRule", + Name: "rul3", + PassThroughPercent: tools.Float64Ptr(-1), + ID: 0, Matches: []MatchCriteriaAP{ { CaseSensitive: false, @@ -3311,7 +3307,6 @@ func TestCreatePolicyVersion(t *testing.T) { ], "name": "rul3", "start": 0, - "useIncomingQueryString": false, "passThroughPercent": -1 } ], @@ -3342,7 +3337,7 @@ func TestCreatePolicyVersion(t *testing.T) { ID: 0, MatchURL: "", Name: "rul3", - PassThroughPercent: -1, + PassThroughPercent: tools.Float64Ptr(-1), Start: 0, Matches: []MatchCriteriaAP{ { @@ -3373,7 +3368,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "vpMatchRule", - PassThroughPercent: 50.50, + PassThroughPercent: tools.Float64Ptr(50.50), Name: "rul3", ID: 0, Matches: []MatchCriteriaVP{ @@ -3403,7 +3398,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "apMatchRule", - PassThroughPercent: 50.50, + PassThroughPercent: tools.Float64Ptr(50.50), Name: "rul3", ID: 0, Matches: []MatchCriteriaAP{ @@ -3469,7 +3464,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "vpMatchRule", - PassThroughPercent: 101, + PassThroughPercent: tools.Float64Ptr(101), Name: "rul3", ID: 0, }, @@ -3488,7 +3483,7 @@ func TestCreatePolicyVersion(t *testing.T) { Start: 0, End: 0, Type: "apMatchRule", - PassThroughPercent: 101, + PassThroughPercent: tools.Float64Ptr(101), Name: "rul3", ID: 0, }, diff --git a/pkg/cloudlets/tools/ptr.go b/pkg/cloudlets/tools/ptr.go index 5584e119..a84c2567 100644 --- a/pkg/cloudlets/tools/ptr.go +++ b/pkg/cloudlets/tools/ptr.go @@ -9,3 +9,8 @@ func IntPtr(i int) *int { func Int64Ptr(i int64) *int64 { return &i } + +// Float64Ptr returns the address of the float64 +func Float64Ptr(f float64) *float64 { + return &f +} From aac2990e7e2286d412d1a2e89859d93d4f42c90d Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Wed, 8 Dec 2021 12:45:59 +0100 Subject: [PATCH 09/17] DXE-44 Fixed problem that only one of "matches" and "matchesAlways" can be specified --- pkg/cloudlets/match_rule.go | 2 +- pkg/cloudlets/policy_version_test.go | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 0595425e..a18ec76f 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -62,7 +62,7 @@ type ( MatchURL string `json:"matchURL,omitempty"` ForwardSettings ForwardSettingsCD `json:"forwardSettings"` Disabled bool `json:"disabled,omitempty"` - MatchesAlways bool `json:"matchesAlways"` + MatchesAlways bool `json:"matchesAlways,omitempty"` } // ForwardSettingsCD represents forward settings for CD aka PR diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index dce9ca68..7a6dbb55 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -1,6 +1,7 @@ package cloudlets import ( + "bytes" "context" "errors" "net/http" @@ -681,6 +682,7 @@ func TestGetPolicyVersion(t *testing.T) { func TestCreatePolicyVersion(t *testing.T) { tests := map[string]struct { request CreatePolicyVersionRequest + requestBody string responseStatus int responseBody string expectedPath string @@ -918,7 +920,6 @@ func TestCreatePolicyVersion(t *testing.T) { Version: 2, }, }, - "201 created, complex ALB with objectMatchValue - object": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1051,7 +1052,6 @@ func TestCreatePolicyVersion(t *testing.T) { Version: 796, }, }, - "201 created, complex ALB with objectMatchValue - simple": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1174,7 +1174,6 @@ func TestCreatePolicyVersion(t *testing.T) { Version: 797, }, }, - "201 created, complex ALB with objectMatchValue - range": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -1545,6 +1544,7 @@ func TestCreatePolicyVersion(t *testing.T) { }, PolicyID: 276858, }, + requestBody: `{"matchRules":[{"name":"rul3","type":"cdMatchRule","matches":[{"matchType":"method","matchOperator":"equals","caseSensitive":true,"negate":false,"objectMatchValue":{"type":"simple","value":["GET"]}}],"forwardSettings":{"originId":"some_origin","percent":10}}]}`, responseStatus: http.StatusCreated, responseBody: `{ "activations": [], @@ -3237,7 +3237,6 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "201 created, complex AP with objectMatchValue - object": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ @@ -3530,6 +3529,13 @@ func TestCreatePolicyVersion(t *testing.T) { mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, test.expectedPath, r.URL.String()) assert.Equal(t, http.MethodPost, r.Method) + if test.requestBody != "" { + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(r.Body) + assert.NoError(t, err) + req := buf.String() + assert.Equal(t, test.requestBody, req) + } w.WriteHeader(test.responseStatus) _, err := w.Write([]byte(test.responseBody)) assert.NoError(t, err) @@ -3610,6 +3616,7 @@ func TestDeletePolicyVersion(t *testing.T) { func TestUpdatePolicyVersion(t *testing.T) { tests := map[string]struct { request UpdatePolicyVersionRequest + requestBody string responseStatus int responseBody string expectedPath string From 3a8b0740c87576861d38fa79a76ce6cd72d81da8 Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Thu, 9 Dec 2021 12:56:14 +0100 Subject: [PATCH 10/17] DXE-44 Edgegrid - rename references for Continuous Deployment (CD) to Phased Release (PR) --- pkg/cloudlets/match_rule.go | 56 ++++++++--------- pkg/cloudlets/match_rule_test.go | 12 ++-- pkg/cloudlets/policy_version_test.go | 90 ++++++++++++++-------------- 3 files changed, 79 insertions(+), 79 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index a18ec76f..0529c4a5 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -51,22 +51,22 @@ type ( Disabled bool `json:"disabled,omitempty"` } - // MatchRuleCD represents a match rule resource for create or update resource - MatchRuleCD struct { + // MatchRulePR represents a match rule resource for create or update resource + MatchRulePR struct { Name string `json:"name,omitempty"` Type MatchRuleType `json:"type,omitempty"` Start int `json:"start,omitempty"` End int `json:"end,omitempty"` ID int64 `json:"id,omitempty"` - Matches []MatchCriteriaCD `json:"matches,omitempty"` + Matches []MatchCriteriaPR `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` - ForwardSettings ForwardSettingsCD `json:"forwardSettings"` + ForwardSettings ForwardSettingsPR `json:"forwardSettings"` Disabled bool `json:"disabled,omitempty"` MatchesAlways bool `json:"matchesAlways,omitempty"` } - // ForwardSettingsCD represents forward settings for CD aka PR - ForwardSettingsCD struct { + // ForwardSettingsPR represents forward settings for CD aka PR + ForwardSettingsPR struct { OriginID string `json:"originId"` Percent int `json:"percent"` } @@ -140,9 +140,9 @@ type ( // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple MatchCriteriaAP MatchCriteria - // MatchCriteriaCD represents a match criteria resource for match rule for cloudlet CD aka PR + // MatchCriteriaPR represents a match criteria resource for match rule for cloudlet CD aka PR // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple - MatchCriteriaCD MatchCriteria + MatchCriteriaPR MatchCriteria // MatchCriteriaER represents a match criteria resource for match rule for cloudlet ER // ObjectMatchValue can contain ObjectMatchValueObject or ObjectMatchValueSimple @@ -206,8 +206,8 @@ const ( MatchRuleTypeALB MatchRuleType = "albMatchRule" // MatchRuleTypeAP represents rule type for AP cloudlets MatchRuleTypeAP MatchRuleType = "apMatchRule" - // MatchRuleTypeCD represents rule type for CD aka PR cloudlets - MatchRuleTypeCD MatchRuleType = "cdMatchRule" + // MatchRuleTypePR represents rule type for PR aka PR cloudlets + MatchRuleTypePR MatchRuleType = "cdMatchRule" // MatchRuleTypeER represents rule type for ER cloudlets MatchRuleTypeER MatchRuleType = "erMatchRule" // MatchRuleTypeFR represents rule type for FR cloudlets @@ -253,8 +253,8 @@ var ( ErrUnmarshallMatchCriteriaALB = errors.New("unmarshalling MatchCriteriaALB") // ErrUnmarshallMatchCriteriaAP is returned when unmarshalling of MatchCriteriaAP fails ErrUnmarshallMatchCriteriaAP = errors.New("unmarshalling MatchCriteriaAP") - // ErrUnmarshallMatchCriteriaCD is returned when unmarshalling of MatchCriteriaCD fails - ErrUnmarshallMatchCriteriaCD = errors.New("unmarshalling MatchCriteriaCD") + // ErrUnmarshallMatchCriteriaPR is returned when unmarshalling of MatchCriteriaPR fails + ErrUnmarshallMatchCriteriaPR = errors.New("unmarshalling MatchCriteriaPR") // ErrUnmarshallMatchCriteriaER is returned when unmarshalling of MatchCriteriaER fails ErrUnmarshallMatchCriteriaER = errors.New("unmarshalling MatchCriteriaER") // ErrUnmarshallMatchCriteriaFR is returned when unmarshalling of MatchCriteriaFR fails @@ -270,7 +270,7 @@ var ( var matchRuleHandlers = map[string]func() MatchRule{ "albMatchRule": func() MatchRule { return &MatchRuleALB{} }, "apMatchRule": func() MatchRule { return &MatchRuleAP{} }, - "cdMatchRule": func() MatchRule { return &MatchRuleCD{} }, + "cdMatchRule": func() MatchRule { return &MatchRulePR{} }, "erMatchRule": func() MatchRule { return &MatchRuleER{} }, "vpMatchRule": func() MatchRule { return &MatchRuleVP{} }, "frMatchRule": func() MatchRule { return &MatchRuleFR{} }, @@ -319,10 +319,10 @@ func (m MatchRuleAP) Validate() error { }.Filter() } -// Validate validates MatchRuleCD -func (m MatchRuleCD) Validate() error { +// Validate validates MatchRulePR +func (m MatchRulePR) Validate() error { return validation.Errors{ - "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypeCD).Error( + "Type": validation.Validate(m.Type, validation.Required, validation.In(MatchRuleTypePR).Error( fmt.Sprintf("value '%s' is invalid. Must be: 'cdMatchRule'", (&m).Type))), "Name": validation.Validate(m.Name, validation.Length(0, 8192)), "Start": validation.Validate(m.Start, validation.Min(0)), @@ -416,8 +416,8 @@ func (m MatchCriteriaAP) Validate() error { }.Filter() } -// Validate validates MatchCriteriaCD -func (m MatchCriteriaCD) Validate() error { +// Validate validates MatchCriteriaPR +func (m MatchCriteriaPR) Validate() error { return validation.Errors{ "MatchType": validation.Validate(m.MatchType, validation.In("header", "hostname", "path", "extension", "query", "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", @@ -555,7 +555,7 @@ func (m MatchRuleAP) cloudletType() string { return "apMatchRule" } -func (m MatchRuleCD) cloudletType() string { +func (m MatchRulePR) cloudletType() string { return "cdMatchRule" } @@ -669,15 +669,15 @@ func (m *MatchCriteriaAP) UnmarshalJSON(b []byte) error { return nil } -// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaCD as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple -func (m *MatchCriteriaCD) UnmarshalJSON(b []byte) error { - // matchCriteriaCD is an alias for MatchCriteriaCD for un-marshalling purposes - type matchCriteriaCD MatchCriteriaCD +// UnmarshalJSON helps to un-marshall field ObjectMatchValue of MatchCriteriaPR as proper instance of *ObjectMatchValueObject or *ObjectMatchValueSimple +func (m *MatchCriteriaPR) UnmarshalJSON(b []byte) error { + // matchCriteriaPR is an alias for MatchCriteriaPR for un-marshalling purposes + type matchCriteriaPR MatchCriteriaPR // populate common attributes using default json unmarshaler using aliased type - err := json.Unmarshal(b, (*matchCriteriaCD)(m)) + err := json.Unmarshal(b, (*matchCriteriaPR)(m)) if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaCD, err) + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaPR, err) } if m.ObjectMatchValue == nil { return nil @@ -685,16 +685,16 @@ func (m *MatchCriteriaCD) UnmarshalJSON(b []byte) error { objectMatchValueTypeName, err := getObjectMatchValueType(m.ObjectMatchValue) if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaCD, err) + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaPR, err) } createObjectMatchValue, ok := simpleObjectMatchValueHandlers[objectMatchValueTypeName] if !ok { - return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaCD, objectMatchValueTypeName) + return fmt.Errorf("%w: objectMatchValue has unexpected type: '%s'", ErrUnmarshallMatchCriteriaPR, objectMatchValueTypeName) } convertedObjectMatchValue, err := convertObjectMatchValue(m.ObjectMatchValue, createObjectMatchValue()) if err != nil { - return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaCD, err) + return fmt.Errorf("%w: %s", ErrUnmarshallMatchCriteriaPR, err) } m.ObjectMatchValue = convertedObjectMatchValue diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index 62ae4940..17ea156c 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -256,8 +256,8 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { `, }, - "invalid objectMatchValue type for CD - range": { - withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaCD: objectMatchValue has unexpected type: 'range'"), + "invalid objectMatchValue type for PR - range": { + withError: errors.New("unmarshalling MatchRules: unmarshalling MatchCriteriaPR: objectMatchValue has unexpected type: 'range'"), responseBody: ` [ { @@ -340,7 +340,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { `, }, - "valid MatchRuleCD": { + "valid MatchRulePR": { responseBody: ` [ { @@ -379,12 +379,12 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { ] `, expectedObject: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", End: 0, ID: 0, MatchURL: "", - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { CaseSensitive: false, MatchOperator: "equals", @@ -405,7 +405,7 @@ func TestUnmarshalJSONMatchRules(t *testing.T) { }, Name: "Rule3", Start: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "fr_test_krk_dc2", Percent: 62, }, diff --git a/pkg/cloudlets/policy_version_test.go b/pkg/cloudlets/policy_version_test.go index 7a6dbb55..dba943c7 100644 --- a/pkg/cloudlets/policy_version_test.go +++ b/pkg/cloudlets/policy_version_test.go @@ -344,7 +344,7 @@ func TestGetPolicyVersion(t *testing.T) { }, }, }, - "200 OK, CD rule": { + "200 OK, PR rule": { request: GetPolicyVersionRequest{ PolicyID: 276858, Version: 6, @@ -406,15 +406,15 @@ func TestGetPolicyVersion(t *testing.T) { Location: "/cloudlets/api/v2/policies/325401/versions/3", MatchRuleFormat: "1.0", MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", End: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "fr_test_krk_dc2", Percent: 11, }, ID: 0, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { CaseSensitive: false, MatchOperator: "equals", @@ -1298,21 +1298,21 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, - "201 created, complex CD": { + "201 created, complex PR": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Start: 0, End: 0, Type: "cdMatchRule", Name: "rul3", ID: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { MatchType: "hostname", MatchValue: "3333.dom", @@ -1336,26 +1336,26 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - &MatchRuleCD{ + &MatchRulePR{ Start: 0, End: 0, Type: "cdMatchRule", Name: "rule 2", MatchURL: "ddd.aaa", ID: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, }, - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", ID: 0, Name: "r1", Start: 0, End: 0, MatchURL: "abc.com", - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, @@ -1459,18 +1459,18 @@ func TestCreatePolicyVersion(t *testing.T) { RulesLocked: false, Version: 6, MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", End: 0, ID: 0, MatchURL: "", Name: "rul3", Start: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { MatchType: "hostname", MatchValue: "3333.dom", @@ -1494,7 +1494,7 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", End: 0, ID: 0, @@ -1502,7 +1502,7 @@ func TestCreatePolicyVersion(t *testing.T) { Name: "rule 2", Start: 0, }, - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", End: 0, ID: 0, @@ -1513,21 +1513,21 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "201 created, complex CD with objectMatchValue - simple": { + "201 created, complex PR with objectMatchValue - simple": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Start: 0, End: 0, Type: "cdMatchRule", Name: "rul3", ID: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { CaseSensitive: true, MatchOperator: "equals", @@ -1606,18 +1606,18 @@ func TestCreatePolicyVersion(t *testing.T) { RulesLocked: false, Version: 6, MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", End: 0, ID: 0, MatchURL: "", Name: "rul3", Start: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { CaseSensitive: true, MatchOperator: "equals", @@ -1633,28 +1633,28 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "201 created, complex CD with objectMatchValue - object": { + "201 created, complex PR with objectMatchValue - object": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Start: 0, End: 0, Type: "cdMatchRule", Name: "rul3", ID: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { MatchOperator: "equals", MatchType: "header", Negate: false, ObjectMatchValue: &ObjectMatchValueObject{ Type: "object", - Name: "CD", + Name: "PR", Options: &Options{ Value: []string{ "text/html*", @@ -1695,7 +1695,7 @@ func TestCreatePolicyVersion(t *testing.T) { "negate": false, "objectMatchValue": { "type": "object", - "name": "CD", + "name": "PR", "options": { "value": [ "text/html*", @@ -1737,25 +1737,25 @@ func TestCreatePolicyVersion(t *testing.T) { RulesLocked: false, Version: 6, MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", End: 0, ID: 0, MatchURL: "", Name: "rul3", Start: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { MatchOperator: "equals", MatchType: "hostname", Negate: false, ObjectMatchValue: &ObjectMatchValueObject{ Type: "object", - Name: "CD", + Name: "PR", Options: &Options{ Value: []string{ "text/html*", @@ -1771,21 +1771,21 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - "validation error, complex CD with unavailable objectMatchValue type - range": { + "validation error, complex PR with unavailable objectMatchValue type - range": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Start: 0, End: 0, Type: "cdMatchRule", Name: "rul3", ID: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { MatchOperator: "equals", MatchType: "header", @@ -1803,21 +1803,21 @@ func TestCreatePolicyVersion(t *testing.T) { }, withError: ErrStructValidation, }, - "validation error, complex CD missing forwardSettings": { + "validation error, complex PR missing forwardSettings": { request: CreatePolicyVersionRequest{ CreatePolicyVersion: CreatePolicyVersion{ MatchRules: MatchRules{ - &MatchRuleCD{ + &MatchRulePR{ Start: 0, End: 0, Type: "cdMatchRule", Name: "rul3", ID: 0, - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, - Matches: []MatchCriteriaCD{ + Matches: []MatchCriteriaPR{ { MatchType: "hostname", MatchValue: "3333.dom", @@ -1841,7 +1841,7 @@ func TestCreatePolicyVersion(t *testing.T) { }, }, }, - &MatchRuleCD{ + &MatchRulePR{ Start: 0, End: 0, Type: "cdMatchRule", @@ -1849,14 +1849,14 @@ func TestCreatePolicyVersion(t *testing.T) { MatchURL: "ddd.aaa", ID: 0, }, - &MatchRuleCD{ + &MatchRulePR{ Type: "cdMatchRule", ID: 0, Name: "r1", Start: 0, End: 0, MatchURL: "abc.com", - ForwardSettings: ForwardSettingsCD{ + ForwardSettings: ForwardSettingsPR{ OriginID: "some_origin", Percent: 10, }, From 8f30e0eccfb357257a2c8ad24aecba7717d125f2 Mon Sep 17 00:00:00 2001 From: Darek Stopka Date: Tue, 14 Dec 2021 16:56:33 +0000 Subject: [PATCH 11/17] DXE-164 Add validation for MatchRules type --- pkg/cloudlets/match_rule.go | 12 ++ pkg/cloudlets/match_rule_test.go | 298 +++++++++++++++++++++++++++++++ 2 files changed, 310 insertions(+) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 0529c4a5..e260dd67 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/edgegriderr" + validation "github.com/go-ozzo/ozzo-validation/v4" ) @@ -291,6 +293,16 @@ var simpleObjectMatchValueHandlers = map[string]func() interface{}{ "simple": func() interface{} { return &ObjectMatchValueSimple{} }, } +// Validate validates MatchRules +func (m MatchRules) Validate() error { + type matchRules MatchRules + + errs := validation.Errors{ + "MatchRules": validation.Validate(matchRules(m), validation.Length(0, 5000)), + } + return edgegriderr.ParseValidationErrors(errs) +} + // Validate validates MatchRuleALB func (m MatchRuleALB) Validate() error { return validation.Errors{ diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index 17ea156c..ec7325fc 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -3,6 +3,7 @@ package cloudlets import ( "encoding/json" "errors" + "strings" "testing" "github.com/stretchr/testify/require" @@ -808,3 +809,300 @@ func TestConvertObjectMatchValue(t *testing.T) { }) } } + +func TestValidateMatchRules(t *testing.T) { + tests := map[string]struct { + input MatchRules + withError string + }{ + "valid match rules ALB": { + input: MatchRules{ + MatchRuleALB{ + Type: "albMatchRule", + ForwardSettings: ForwardSettingsALB{ + OriginID: "testOriginID", + }, + }, + MatchRuleALB{ + Type: "albMatchRule", + Start: 1, + End: 2, + ForwardSettings: ForwardSettingsALB{ + OriginID: "testOriginID", + }, + }, + }, + }, + "invalid match rules ALB": { + input: MatchRules{ + MatchRuleALB{ + Type: "matchRule", + }, + MatchRuleALB{ + Type: "albMatchRule", + Start: -1, + End: -1, + ForwardSettings: ForwardSettingsALB{ + OriginID: "testOriginID", + }, + }, + }, + withError: ` +MatchRules[0]: { + ForwardSettings.OriginID: cannot be blank + Type: value 'matchRule' is invalid. Must be: 'albMatchRule' +} +MatchRules[1]: { + End: must be no less than 0 + Start: must be no less than 0 +}`, + }, + "valid match rules AP": { + input: MatchRules{ + MatchRuleAP{ + Type: "apMatchRule", + PassThroughPercent: tools.Float64Ptr(-1), + }, + MatchRuleAP{ + Type: "apMatchRule", + PassThroughPercent: tools.Float64Ptr(50.5), + }, + MatchRuleAP{ + Type: "apMatchRule", + PassThroughPercent: tools.Float64Ptr(0), + }, + MatchRuleAP{ + Type: "apMatchRule", + PassThroughPercent: tools.Float64Ptr(100), + }, + }, + }, + "invalid match rules AP": { + input: MatchRules{ + MatchRuleAP{ + Type: "matchRule", + }, + MatchRuleAP{ + Type: "apMatchRule", + PassThroughPercent: tools.Float64Ptr(100.1), + }, + MatchRuleAP{ + Type: "apMatchRule", + PassThroughPercent: tools.Float64Ptr(-1.1), + }, + }, + withError: ` +MatchRules[0]: { + PassThroughPercent: cannot be blank + Type: value 'matchRule' is invalid. Must be: 'apMatchRule' +} +MatchRules[1]: { + PassThroughPercent: must be no greater than 100 +} +MatchRules[2]: { + PassThroughPercent: must be no less than -1 +}`, + }, + "valid match rules CD": { + input: MatchRules{ + MatchRulePR{ + Type: "cdMatchRule", + ForwardSettings: ForwardSettingsPR{ + OriginID: "testOriginID", + Percent: 100, + }, + }, + MatchRulePR{ + Type: "cdMatchRule", + ForwardSettings: ForwardSettingsPR{ + OriginID: "testOriginID", + Percent: 1, + }, + }, + }, + }, + "invalid match rules CD": { + input: MatchRules{ + MatchRulePR{ + Type: "matchRule", + }, + MatchRulePR{ + Type: "cdMatchRule", + ForwardSettings: ForwardSettingsPR{}, + }, + MatchRulePR{ + Type: "cdMatchRule", + ForwardSettings: ForwardSettingsPR{ + OriginID: "testOriginID", + Percent: 101, + }, + }, + MatchRulePR{ + Type: "cdMatchRule", + ForwardSettings: ForwardSettingsPR{ + OriginID: "testOriginID", + Percent: -1, + }, + }, + MatchRulePR{ + Type: "cdMatchRule", + ForwardSettings: ForwardSettingsPR{ + OriginID: "testOriginID", + Percent: 0, + }, + }, + }, + withError: ` +MatchRules[0]: { + ForwardSettings.OriginID: cannot be blank + ForwardSettings.Percent: cannot be blank + Type: value 'matchRule' is invalid. Must be: 'cdMatchRule' +} +MatchRules[1]: { + ForwardSettings.OriginID: cannot be blank + ForwardSettings.Percent: cannot be blank +} +MatchRules[2]: { + ForwardSettings.Percent: must be no greater than 100 +} +MatchRules[3]: { + ForwardSettings.Percent: must be no less than 1 +} +MatchRules[4]: { + ForwardSettings.Percent: cannot be blank +}`, + }, + "valid match rules ER": { + input: MatchRules{ + MatchRuleER{ + Type: "erMatchRule", + RedirectURL: "abc.com", + UseRelativeURL: "none", + StatusCode: 301, + }, + MatchRuleER{ + Type: "erMatchRule", + RedirectURL: "abc.com", + StatusCode: 301, + }, + }, + }, + "invalid match rules ER": { + input: MatchRules{ + MatchRuleER{ + Type: "matchRule", + }, + MatchRuleER{ + Type: "erMatchRule", + RedirectURL: "abc.com", + UseRelativeURL: "test", + StatusCode: 404, + }, + }, + withError: ` +MatchRules[0]: { + RedirectURL: cannot be blank + StatusCode: cannot be blank + Type: value 'matchRule' is invalid. Must be: 'erMatchRule' +} +MatchRules[1]: { + StatusCode: value '404' is invalid. Must be one of: 301, 302, 303, 307 or 308 + UseRelativeURL: value 'test' is invalid. Must be one of: 'none', 'copy_scheme_hostname', 'relative_url' or '' (empty) +}`, + }, + "valid match rules FR": { + input: MatchRules{ + MatchRuleFR{ + Type: "frMatchRule", + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "test", + OriginID: "testOriginID", + }, + }, + MatchRuleFR{ + Type: "frMatchRule", + ForwardSettings: ForwardSettingsFR{ + PathAndQS: "test", + OriginID: "testOriginID", + }, + }, + }, + }, + "invalid match rules FR": { + input: MatchRules{ + MatchRuleFR{ + Type: "matchRule", + }, + MatchRuleFR{ + Type: "frMatchRule", + ForwardSettings: ForwardSettingsFR{ + OriginID: "testOriginID", + PathAndQS: "", + }, + }, + }, + withError: ` +MatchRules[0]: { + Type: value 'matchRule' is invalid. Must be: 'frMatchRule' +}`, + }, + "valid match rules VP": { + input: MatchRules{ + MatchRuleVP{ + Type: "vpMatchRule", + PassThroughPercent: tools.Float64Ptr(-1), + }, + MatchRuleVP{ + Type: "vpMatchRule", + PassThroughPercent: tools.Float64Ptr(50.5), + }, + MatchRuleVP{ + Type: "vpMatchRule", + PassThroughPercent: tools.Float64Ptr(0), + }, + MatchRuleVP{ + Type: "vpMatchRule", + PassThroughPercent: tools.Float64Ptr(100), + }, + }, + }, + "invalid match rules VP": { + input: MatchRules{ + MatchRuleVP{ + Type: "matchRule", + }, + MatchRuleVP{ + Type: "vpMatchRule", + PassThroughPercent: tools.Float64Ptr(100.1), + }, + MatchRuleVP{ + Type: "vpMatchRule", + PassThroughPercent: tools.Float64Ptr(-1.1), + }, + }, + withError: ` +MatchRules[0]: { + PassThroughPercent: cannot be blank + Type: value 'matchRule' is invalid. Must be: 'vpMatchRule' +} +MatchRules[1]: { + PassThroughPercent: must be no greater than 100 +} +MatchRules[2]: { + PassThroughPercent: must be no less than -1 +}`, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + err := test.input.Validate() + if test.withError != "" { + require.Error(t, err) + assert.Equal(t, strings.TrimPrefix(test.withError, "\n"), err.Error()) + return + } + require.NoError(t, err) + }) + } +} From 1e6c260fe05df0f65441ee16907ac2e33ad9d226 Mon Sep 17 00:00:00 2001 From: Darek Stopka Date: Mon, 20 Dec 2021 13:47:42 +0000 Subject: [PATCH 12/17] DXE-254 Fix validation for matchValue and objectMatchValue --- pkg/cloudlets/match_rule.go | 36 ++++++++++----- pkg/cloudlets/match_rule_test.go | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 12 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index e260dd67..84b455fd 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -402,12 +402,14 @@ func (m MatchCriteriaALB) Validate() error { "deviceCharacteristics", "extension", "header", "hostname", "method", "path", "protocol", "proxy", "query", "regioncode", "range").Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'clientip', 'continent', 'cookie', 'countrycode', 'deviceCharacteristics', "+ "'extension', 'header', 'hostname', 'method', 'path', 'protocol', 'proxy', 'query', 'regioncode', 'range' or '' (empty)", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192), validation.Required.When(m.ObjectMatchValue == nil)), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(1, 8192), validation.Required.When(m.ObjectMatchValue == nil).Error("cannot be blank when ObjectMatchValue is blank"), + validation.Empty.When(m.ObjectMatchValue != nil).Error("must be blank when ObjectMatchValue is set")), "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrRangeOrObjectValidation)), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == "").Error("cannot be blank when MatchValue is blank"), + validation.Empty.When(m.MatchValue != "").Error("must be blank when MatchValue is set"), validation.By(objectMatchValueSimpleOrRangeOrObjectValidation)), }.Filter() } @@ -419,12 +421,14 @@ func (m MatchCriteriaAP) Validate() error { "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'cookie', "+ "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(1, 8192), validation.Required.When(m.ObjectMatchValue == nil).Error("cannot be blank when ObjectMatchValue is blank"), + validation.Empty.When(m.ObjectMatchValue != nil).Error("must be blank when ObjectMatchValue is set")), "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == "").Error("cannot be blank when MatchValue is blank"), + validation.Empty.When(m.MatchValue != "").Error("must be blank when MatchValue is set"), validation.By(objectMatchValueSimpleOrObjectValidation)), }.Filter() } @@ -436,12 +440,14 @@ func (m MatchCriteriaPR) Validate() error { "method", "proxy").Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'cookie', "+ "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(1, 8192), validation.Required.When(m.ObjectMatchValue == nil).Error("cannot be blank when ObjectMatchValue is blank"), + validation.Empty.When(m.ObjectMatchValue != nil).Error("must be blank when ObjectMatchValue is set")), "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == "").Error("cannot be blank when MatchValue is blank"), + validation.Empty.When(m.MatchValue != "").Error("must be blank when MatchValue is set"), validation.By(objectMatchValueSimpleOrObjectValidation)), }.Filter() } @@ -452,12 +458,14 @@ func (m MatchCriteriaER) Validate() error { "regex", "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'regex', 'cookie', "+ "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy' or '' (empty)", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(1, 8192), validation.Required.When(m.ObjectMatchValue == nil).Error("cannot be blank when ObjectMatchValue is blank"), + validation.Empty.When(m.ObjectMatchValue != nil).Error("must be blank when ObjectMatchValue is set")), "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == "").Error("cannot be blank when MatchValue is blank"), + validation.Empty.When(m.MatchValue != "").Error("must be blank when MatchValue is set"), validation.By(objectMatchValueSimpleOrObjectValidation)), }.Filter() } @@ -468,12 +476,14 @@ func (m MatchCriteriaFR) Validate() error { "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'regex', 'cookie', "+ "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(1, 8192), validation.Required.When(m.ObjectMatchValue == nil).Error("cannot be blank when ObjectMatchValue is blank"), + validation.Empty.When(m.ObjectMatchValue != nil).Error("must be blank when ObjectMatchValue is set")), "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == "").Error("cannot be blank when MatchValue is blank"), + validation.Empty.When(m.MatchValue != "").Error("must be blank when MatchValue is set"), validation.By(objectMatchValueSimpleOrObjectValidation)), }.Filter() } @@ -484,12 +494,14 @@ func (m MatchCriteriaVP) Validate() error { "cookie", "deviceCharacteristics", "clientip", "continent", "countrycode", "regioncode", "protocol", "method", "proxy").Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'header', 'hostname', 'path', 'extension', 'query', 'cookie', "+ "'deviceCharacteristics', 'clientip', 'continent', 'countrycode', 'regioncode', 'protocol', 'method', 'proxy'", (&m).MatchType))), - "MatchValue": validation.Validate(m.MatchValue, validation.Length(0, 8192)), + "MatchValue": validation.Validate(m.MatchValue, validation.Length(1, 8192), validation.Required.When(m.ObjectMatchValue == nil).Error("cannot be blank when ObjectMatchValue is blank"), + validation.Empty.When(m.ObjectMatchValue != nil).Error("must be blank when ObjectMatchValue is set")), "MatchOperator": validation.Validate(m.MatchOperator, validation.In(MatchOperatorContains, MatchOperatorExists, MatchOperatorEquals).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'contains', 'exists', 'equals' or '' (empty)", (&m).MatchOperator))), "CheckIPs": validation.Validate(m.CheckIPs, validation.In(CheckIPsConnectingIP, CheckIPsXFFHeaders, CheckIPsConnectingIPXFFHeaders).Error( fmt.Sprintf("value '%s' is invalid. Must be one of: 'CONNECTING_IP', 'XFF_HEADERS', 'CONNECTING_IP XFF_HEADERS' or '' (empty)", (&m).CheckIPs))), - "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == ""), validation.By(objectMatchValueSimpleOrObjectValidation)), + "ObjectMatchValue": validation.Validate(m.ObjectMatchValue, validation.Required.When(m.MatchValue == "").Error("cannot be blank when MatchValue is blank"), + validation.Empty.When(m.MatchValue != "").Error("must be blank when MatchValue is set"), validation.By(objectMatchValueSimpleOrObjectValidation)), }.Filter() } diff --git a/pkg/cloudlets/match_rule_test.go b/pkg/cloudlets/match_rule_test.go index ec7325fc..3da7ee22 100644 --- a/pkg/cloudlets/match_rule_test.go +++ b/pkg/cloudlets/match_rule_test.go @@ -1090,6 +1090,84 @@ MatchRules[1]: { } MatchRules[2]: { PassThroughPercent: must be no less than -1 +}`, + }, + "valid match criteria - matchValue": { + input: MatchRules{ + MatchRuleER{ + Type: "erMatchRule", + RedirectURL: "abc.com", + StatusCode: 301, + Matches: []MatchCriteriaER{ + { + MatchType: "method", + MatchOperator: "equals", + CheckIPs: "CONNECTING_IP", + MatchValue: "https", + }, + }, + }, + }, + }, + "valid match criteria - object match value": { + input: MatchRules{ + MatchRuleER{ + Type: "erMatchRule", + RedirectURL: "abc.com", + StatusCode: 301, + Matches: []MatchCriteriaER{ + { + MatchType: "header", + MatchOperator: "equals", + CheckIPs: "CONNECTING_IP", + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{ + "GET", + }, + }, + }, + }, + }, + }, + }, + "invalid match criteria - matchValue and omv combinations": { + input: MatchRules{ + MatchRuleER{ + Type: "erMatchRule", + RedirectURL: "abc.com", + StatusCode: 301, + Matches: []MatchCriteriaER{ + { + MatchType: "header", + MatchOperator: "equals", + CheckIPs: "CONNECTING_IP", + ObjectMatchValue: &ObjectMatchValueSimple{ + Type: "simple", + Value: []string{ + "GET", + }, + }, + MatchValue: "GET", + }, + { + MatchType: "header", + MatchOperator: "equals", + CheckIPs: "CONNECTING_IP", + }, + }, + }, + }, + withError: ` +MatchRules[0]: { + Matches[0]: { + MatchValue: must be blank when ObjectMatchValue is set + ObjectMatchValue: must be blank when MatchValue is set + } + Matches[1]: { + MatchValue: cannot be blank when ObjectMatchValue is blank + ObjectMatchValue: cannot be blank when MatchValue is blank + } }`, }, } From 51c8ce5455278f3b82d1ccc81aa17e721686e4db Mon Sep 17 00:00:00 2001 From: Mateusz Jakubiec Date: Tue, 21 Dec 2021 11:42:32 +0100 Subject: [PATCH 13/17] DXE-199 upgrade AkamaiOPEN-edgegrid-golang to go 1.17 --- go.mod | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0b9904bc..5e69245c 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,26 @@ module github.com/akamai/AkamaiOPEN-edgegrid-golang/v2 -go 1.14 +go 1.17 require ( github.com/apex/log v1.9.0 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/google/uuid v1.1.1 - github.com/kr/text v0.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/smartystreets/goconvey v1.6.4 // indirect github.com/spf13/cast v1.3.1 github.com/stretchr/testify v1.6.1 github.com/tj/assert v0.0.3 - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.51.1 ) + +require ( + github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/smartystreets/goconvey v1.6.4 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect +) From a00f2c50c88c68e97c552fac8d01033d48e996e6 Mon Sep 17 00:00:00 2001 From: Piyush Kaushik Date: Mon, 27 Dec 2021 08:38:58 +0000 Subject: [PATCH 14/17] DXE-259 start end fields should be defined explicitly as int64 Merge in DEVEXP/akamaiopen-edgegrid-golang from feature/DXE-259-start-end-fields-should-be-defined-explicitly-as-int64 to feature/sp-cloudlets2 --- pkg/cloudlets/match_rule.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/cloudlets/match_rule.go b/pkg/cloudlets/match_rule.go index 84b455fd..a7eae021 100644 --- a/pkg/cloudlets/match_rule.go +++ b/pkg/cloudlets/match_rule.go @@ -25,8 +25,8 @@ type ( MatchRuleALB struct { Name string `json:"name,omitempty"` Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` ID int64 `json:"id,omitempty"` Matches []MatchCriteriaALB `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` @@ -44,8 +44,8 @@ type ( MatchRuleAP struct { Name string `json:"name,omitempty"` Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` ID int64 `json:"id,omitempty"` Matches []MatchCriteriaAP `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` @@ -57,8 +57,8 @@ type ( MatchRulePR struct { Name string `json:"name,omitempty"` Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` ID int64 `json:"id,omitempty"` Matches []MatchCriteriaPR `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` @@ -77,8 +77,8 @@ type ( MatchRuleER struct { Name string `json:"name,omitempty"` Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` ID int64 `json:"id,omitempty"` Matches []MatchCriteriaER `json:"matches,omitempty"` UseRelativeURL string `json:"useRelativeUrl,omitempty"` @@ -94,8 +94,8 @@ type ( MatchRuleFR struct { Name string `json:"name,omitempty"` Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` ID int64 `json:"id,omitempty"` Matches []MatchCriteriaFR `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` @@ -114,8 +114,8 @@ type ( MatchRuleVP struct { Name string `json:"name,omitempty"` Type MatchRuleType `json:"type,omitempty"` - Start int `json:"start,omitempty"` - End int `json:"end,omitempty"` + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` ID int64 `json:"id,omitempty"` Matches []MatchCriteriaVP `json:"matches,omitempty"` MatchURL string `json:"matchURL,omitempty"` From e6c35e1ca6a6b5c0fba3542e5004129c0ccab824 Mon Sep 17 00:00:00 2001 From: Darek Stopka Date: Tue, 28 Dec 2021 15:51:31 +0100 Subject: [PATCH 15/17] DXE-275 Add required validation to network parameter Required validation added to network parameter in: - LoadBalancerVersionActivation - ActivatePolicyVersionRequest --- pkg/cloudlets/loadbalancer_activation.go | 4 +-- pkg/cloudlets/loadbalancer_activation_test.go | 9 ++++-- pkg/cloudlets/policy_version_activation.go | 3 +- .../policy_version_activation_test.go | 28 +++++++++++++------ 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/pkg/cloudlets/loadbalancer_activation.go b/pkg/cloudlets/loadbalancer_activation.go index 593d9b1e..0253a44f 100644 --- a/pkg/cloudlets/loadbalancer_activation.go +++ b/pkg/cloudlets/loadbalancer_activation.go @@ -120,8 +120,8 @@ func (v ListLoadBalancerActivationsRequest) Validate() error { //Validate validates LoadBalancerVersionActivation Struct func (v LoadBalancerVersionActivation) Validate() error { return validation.Errors{ - "Network": validation.Validate(v.Network, validation.In(LoadBalancerActivationNetworkStaging, LoadBalancerActivationNetworkProduction).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'STAGING', 'PRODUCTION' or '' (empty)", v.Network))), + "Network": validation.Validate(v.Network, validation.Required, validation.In(LoadBalancerActivationNetworkStaging, LoadBalancerActivationNetworkProduction).Error( + fmt.Sprintf("value '%s' is invalid. Must be one of: 'STAGING' or 'PRODUCTION'", v.Network))), "Version": validation.Validate(v.Version, validation.Min(0)), }.Filter() } diff --git a/pkg/cloudlets/loadbalancer_activation_test.go b/pkg/cloudlets/loadbalancer_activation_test.go index 042d6e13..493add5e 100644 --- a/pkg/cloudlets/loadbalancer_activation_test.go +++ b/pkg/cloudlets/loadbalancer_activation_test.go @@ -247,18 +247,21 @@ func TestActivateLoadBalancerVersion(t *testing.T) { assert.True(t, errors.Is(err, want), "want: %s; got: %s", want, err) }, }, - "Validation Error": { + "Validation Errors": { params: ActivateLoadBalancerVersionRequest{ OriginID: "", Async: false, LoadBalancerVersionActivation: LoadBalancerVersionActivation{ - Network: LoadBalancerActivationNetworkStaging, + Network: "", DryRun: false, - Version: 1, + Version: -1, }, }, responseStatus: http.StatusInternalServerError, withError: func(t *testing.T, err error) { + assert.Containsf(t, err.Error(), "Network: cannot be blank", "want: %s; got: %s", ErrStructValidation, err) + assert.Containsf(t, err.Error(), "OriginID: cannot be blank", "want: %s; got: %s", ErrStructValidation, err) + assert.Containsf(t, err.Error(), "Version: must be no less than 0", "want: %s; got: %s", ErrStructValidation, err) assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) }, }, diff --git a/pkg/cloudlets/policy_version_activation.go b/pkg/cloudlets/policy_version_activation.go index b806d4b1..37bd9119 100644 --- a/pkg/cloudlets/policy_version_activation.go +++ b/pkg/cloudlets/policy_version_activation.go @@ -87,8 +87,9 @@ func (r ActivatePolicyVersionRequest) Validate() error { "RequestBody.AdditionalPropertyNames": validation.Validate(r.PolicyVersionActivation.AdditionalPropertyNames, validation.Required), "RequestBody.Network": validation.Validate( r.PolicyVersionActivation.Network, + validation.Required, validation.In(PolicyActivationNetworkStaging, PolicyActivationNetworkProduction).Error( - fmt.Sprintf("value '%s' is invalid. Must be one of: 'staging', 'prod' or '' (empty)", (&r).PolicyVersionActivation.Network)), + fmt.Sprintf("value '%s' is invalid. Must be one of: 'staging' or 'prod'", (&r).PolicyVersionActivation.Network)), ), } return edgegriderr.ParseValidationErrors(errs) diff --git a/pkg/cloudlets/policy_version_activation_test.go b/pkg/cloudlets/policy_version_activation_test.go index 4b023edd..14fce531 100644 --- a/pkg/cloudlets/policy_version_activation_test.go +++ b/pkg/cloudlets/policy_version_activation_test.go @@ -5,7 +5,6 @@ import ( "errors" "net/http" "net/http/httptest" - "regexp" "testing" "github.com/stretchr/testify/assert" @@ -154,7 +153,7 @@ func TestActivatePolicyVersion(t *testing.T) { uri string responseBody string expectedActivation PolicyVersionActivation - withError *regexp.Regexp + withError func(*testing.T, error) }{ "200 Policy version activation": { responseStatus: http.StatusOK, @@ -182,7 +181,9 @@ func TestActivatePolicyVersion(t *testing.T) { }, "any request validation error": { parameters: ActivatePolicyVersionRequest{}, - withError: regexp.MustCompile(ErrStructValidation.Error()), + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + }, }, "any kind of server error": { responseStatus: http.StatusInternalServerError, @@ -194,7 +195,9 @@ func TestActivatePolicyVersion(t *testing.T) { AdditionalPropertyNames: []string{"www.rc-cloudlet.com"}, }, }, - withError: regexp.MustCompile(ErrActivatePolicyVersion.Error()), + withError: func(t *testing.T, err error) { + assert.True(t, errors.Is(err, ErrActivatePolicyVersion), "want: %s; got: %s", ErrActivatePolicyVersion, err) + }, }, "property name not existing": { responseStatus: http.StatusBadRequest, @@ -206,7 +209,10 @@ func TestActivatePolicyVersion(t *testing.T) { AdditionalPropertyNames: []string{"www.rc-cloudlet.com"}, }, }, - withError: regexp.MustCompile(`"Requested propertyName \\"XYZ\\" does not exist"`), + withError: func(t *testing.T, err error) { + assert.Containsf(t, err.Error(), "Requested propertyName \\\"XYZ\\\" does not exist", "want: %s; got: %s", ErrActivatePolicyVersion, err) + assert.True(t, errors.Is(err, ErrActivatePolicyVersion), "want: %s; got: %s", ErrActivatePolicyVersion, err) + }, responseBody: ` { "detail": "Requested propertyName \"XYZ\" does not exist", @@ -217,17 +223,21 @@ func TestActivatePolicyVersion(t *testing.T) { } `, }, - "empty property names": { + "validation errors": { responseStatus: http.StatusBadRequest, parameters: ActivatePolicyVersionRequest{ PolicyID: 1234, Version: 1, PolicyVersionActivation: PolicyVersionActivation{ - Network: PolicyActivationNetworkStaging, + Network: "", AdditionalPropertyNames: []string{}, }, }, - withError: regexp.MustCompile("struct validation:\nRequestBody.AdditionalPropertyNames: cannot be blank"), + withError: func(t *testing.T, err error) { + assert.Containsf(t, err.Error(), "RequestBody.Network: cannot be blank", "want: %s; got: %s", ErrStructValidation, err) + assert.Containsf(t, err.Error(), "RequestBody.AdditionalPropertyNames: cannot be blank", "want: %s; got: %s", ErrStructValidation, err) + assert.True(t, errors.Is(err, ErrStructValidation), "want: %s; got: %s", ErrStructValidation, err) + }, }, } @@ -242,7 +252,7 @@ func TestActivatePolicyVersion(t *testing.T) { client := mockAPIClient(t, mockServer) err := client.ActivatePolicyVersion(context.Background(), test.parameters) if test.withError != nil { - assert.True(t, test.withError.MatchString(err.Error()), "want: %s; got: %s", test.withError, err) + test.withError(t, err) return } require.NoError(t, err) From 4a707675dd46019296f7d35267aa41111eeb9de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3pez=20L=C3=B3pez=2C=20Roberto?= Date: Wed, 19 Jan 2022 12:31:57 +0100 Subject: [PATCH 16/17] DXE-334 changelog for release 2.9.0 --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c60ab1a7..53aeaf75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # EDGEGRID GOLANG RELEASE NOTES -## 2.9.0 (Jan. 11, 2022) +## 2.9.0 (Jan. 24, 2022) #### FEATURES/ENHANCEMENTS: +* CLOUDLETS + * Support for VP cloudlet type (Visitor Prioritization) + * Support for CD cloudlet type (Continuous Deployment / Phased Release) + * Support for FR cloudlet type (Forward Rewrite) + * Support for AP cloudlet type (API Prioritization) + * APPSEC * Add support for Evasive Path Match feature * Deprecate individual policy protection interface methods @@ -90,7 +96,7 @@ ## 2.4.0 (Mar 29, 2021) PAPI - Secure by default -*PAPI +* PAPI * Support to provision default certs as part of hostnames request * New cert status object in hostnames response if it exists From c2b3bf2aacb179aa31694ed73e686b2adb164f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=B3pez=20L=C3=B3pez=2C=20Roberto?= Date: Thu, 20 Jan 2022 09:53:56 +0100 Subject: [PATCH 17/17] DXE-202 retract edgegrid v2.8.0 --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 5e69245c..2a526f20 100644 --- a/go.mod +++ b/go.mod @@ -24,3 +24,5 @@ require ( gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect ) + +retract v2.8.0 \ No newline at end of file