diff --git a/configs/testwaf.json b/configs/testwaf.json new file mode 100644 index 0000000..5e09481 --- /dev/null +++ b/configs/testwaf.json @@ -0,0 +1,239 @@ +{ + "policy": { + "app-protection": { + "enabled": true + }, + "applicationLanguage": "utf-8", + "caseInsensitive": true, + "cookie-settings": { + "maximumCookieHeaderLength": "4096" + }, + "csrf-protection": { + "enabled": false + }, + "csrf-urls": [ + { + "enforcementAction": "verify-origin", + "method": "POST", + "requiredParameters": "ignore", + "url": "*", + "wildcardOrder": 1 + } + ], + "data-guard": { + "enabled": false, + "enforcementMode": "ignore-urls-in-list" + }, + "description": "", + "dos-protection": { + "behavioral-dos": { + "badActorDetection": { + "enableTlsIndexing": true, + "enabled": true + }, + "enableHttpSignatures": true, + "enableTlsSignatures": false, + "mitigationLevel": "standard" + }, + "enabled": false + }, + "enablePassiveMode": false, + "enforcementMode": "blocking", + "fullPath": "/Common/Rating-Based-Template", + "general": { + "allowedResponseCodes": [ + 400, + 401, + 403, + 404, + 405, + 406, + 407, + 415, + 417, + 503 + ], + "enableEventCorrelation": true, + "enforcementReadinessPeriod": 7, + "maskCreditCardNumbersInRequest": true, + "pathParameterHandling": "as-parameters", + "triggerAsmIruleEvent": "disabled", + "trustXff": false, + "useDynamicSessionIdInUrl": false + }, + "header-settings": { + "maximumHttpHeaderLength": "8192" + }, + "login-enforcement": { + "expirationTimePeriod": "disabled" + }, + "name": "test", + "performStaging": true, + "policy-builder": { + "enableFullPolicyInspection": true, + "enableTrustedTrafficSiteChangeTracking": true, + "enableUntrustedTrafficSiteChangeTracking": true, + "inactiveEntityInactivityDurationInDays": 90, + "learnFromResponses": false, + "learnInactiveEntities": false, + "learnOnlyFromNonBotTraffic": true, + "learningMode": "on-demand", + "responseStatusCodes": [ + "2xx", + "3xx", + "1xx" + ], + "trafficTighten": { + "maxModificationSuggestionScore": 50, + "minDaysBetweenSamples": 1, + "totalRequests": 15000 + }, + "trustAllIps": false, + "trustedTrafficLoosen": { + "differentSources": 1, + "maxDaysBetweenSamples": 7, + "minHoursBetweenSamples": 0 + }, + "trustedTrafficSiteChangeTracking": { + "differentSources": 1, + "maxDaysBetweenSamples": 7, + "minMinutesBetweenSamples": 0 + }, + "untrustedTrafficLoosen": { + "differentSources": 20, + "maxDaysBetweenSamples": 7, + "minHoursBetweenSamples": 1 + }, + "untrustedTrafficSiteChangeTracking": { + "differentSources": 10, + "maxDaysBetweenSamples": 7, + "minMinutesBetweenSamples": 20 + } + }, + "policy-builder-central-configuration": { + "buildingMode": "local", + "eventCorrelationMode": "local" + }, + "policy-builder-cookie": { + "collapseCookiesIntoOneEntity": false, + "enforceUnmodifiedCookies": false, + "learnExplicitCookies": "never", + "maximumCookies": 100 + }, + "policy-builder-filetype": { + "learnExplicitFiletypes": "never", + "maximumFileTypes": 100 + }, + "policy-builder-header": { + "maximumHosts": 10000, + "validHostNames": false + }, + "policy-builder-parameter": { + "classifyParameters": false, + "collapseParametersIntoOneEntity": false, + "dynamicParameters": { + "allHiddenFields": false, + "formParameters": false, + "linkParameters": false, + "uniqueValueSets": 10 + }, + "learnExplicitParameters": "never", + "maximumParameters": 10000, + "parameterLearningLevel": "global", + "parametersIntegerValue": false + }, + "policy-builder-redirection-protection": { + "learnExplicitRedirectionDomains": "never", + "maximumRedirectionDomains": 100 + }, + "policy-builder-server-technologies": { + "enableServerTechnologiesDetection": false + }, + "policy-builder-sessions-and-logins": { + "learnLoginPage": false + }, + "policy-builder-url": { + "classifyUrls": false, + "classifyWebsocketUrls": false, + "collapseUrlsIntoOneEntity": false, + "learnExplicitUrls": "never", + "learnExplicitWebsocketUrls": "never", + "learnMethodsOnUrls": false, + "maximumUrls": 100, + "maximumWebsocketUrls": 100, + "wildcardUrlFiletypes": [ + "bmp", + "wav", + "swf", + "gif", + "pcx", + "pdf", + "png", + "jpeg", + "ico", + "jpg" + ] + }, + "protocolIndependent": true, + "redirection-protection": { + "redirectionProtectionEnabled": false + }, + "request-loggers": [ + { + "destination": "toda", + "escapingCharacters": [ + { + "from": "\"", + "to": "\\\"" + } + ], + "filter": [ + { + "field": "enforcementState.hasViolations", + "values": [ + true + ] + } + ], + "formatString": "{\"unit_hostname\":\"%unit_hostname%\",\"management_ip_address\":\"%management_ip_address%\",\"management_ip_address_2\":\"%management_ip_address_2%\",\"http_class_name\":\"%http_class_name%\",\"web_application_name\":\"%http_class_name%\",\"policy_name\":\"%policy_name%\",\"policy_apply_date\":\"%policy_apply_date%Z\",\"violations\":\"%violations%\",\"support_id\":\"%support_id%\",\"request_status\":\"%request_status%\",\"response_code\":\"%response_code%\",\"ip_client\":\"%ip_client%\",\"route_domain\":\"%route_domain%\",\"method\":\"%method%\",\"protocol\":\"%protocol%\",\"query_string\":\"%query_string%\",\"x_forwarded_for_header_value\":\"%x_forwarded_for_header_value%\",\"sig_ids\":\"%sig_ids%\",\"sig_names\":\"%sig_names%\",\"date_time\":\"%date_time%Z\",\"severity\":\"%severity%\",\"attack_type\":\"%attack_type%\",\"geo_location\":\"%geo_location%\",\"ip_address_intelligence\":\"%ip_address_intelligence%\",\"username\":\"%username%\",\"session_id\":\"%session_id%\",\"src_port\":\"%src_port%\",\"dest_port\":\"%dest_port%\",\"dest_ip\":\"%dest_ip%\",\"sub_violations\":\"%sub_violations%\",\"virus_name\":\"%virus_name%\",\"microservice\":\"%microservice%\",\"tap_event_id\":\"%tap_event_id%\",\"tap_vid\":\"%tap_vid%\",\"uri\":\"%uri%\",\"violation_details\":\"%violation_details%\",\"violation_rating\":\"%violation_rating%\",\"websocket_direction\":\"%websocket_direction%\",\"websocket_message_type\":\"%websocket_message_type%\",\"compression_method\":\"%compression_method%\",\"device_id\":\"%device_id%\",\"staged_sig_ids\":\"%staged_sig_ids%\",\"staged_sig_names\":\"%staged_sig_names%\",\"threat_campaign_names\":\"%threat_campaign_names%\",\"staged_threat_campaign_names\":\"%staged_threat_campaign_names%\",\"blocking_exception_reason\":\"%blocking_exception_reason%\",\"mobile_application_name\":\"%mobile_application_name%\",\"mobile_application_version\":\"%mobile_application_version%\",\"client_type\":\"%client_type%\",\"captcha_result\":\"%captcha_result%\",\"headers\":\"%headers%\",\"fragment\":\"%fragment%\",\"sig_cves\":\"%sig_cves%\",\"staged_sig_cves\":\"%staged_sig_cves%\",\"avr_id\":\"%avr_id%\",\"ip_with_route_domain\":\"%ip_with_route_domain%\",\"is_truncated\":\"%is_truncated%\",\"sig_set_names\":\"%sig_set_names%\",\"slot_number\":\"%slot_number%\",\"staged_sig_set_names\":\"%staged_sig_set_names%\",\"vs_name\":\"%vs_name%\",\"login_result\":\"%login_result%\",\"request\":\"%request%\",\"response\":\"%response%\",\"bot_signature_name\":\"%bot_signature_name%\",\"bot_anomalies\":\"%bot_anomalies%\",\"enforced_bot_anomalies\":\"%enforced_bot_anomalies%\",\"client_class\":\"%client_class%\",\"bot_category\":\"%bot_category%\",\"policy_builder_data\":\"%policy_builder_data%\"}", + "maxMessageSize": 65336, + "name": "waf-traffic-request" + } + ], + "sensitive-parameters": [ + { + "name": "password" + } + ], + "signature-sets": [ + { + "alarm": true, + "block": false, + "learn": true, + "name": "High Accuracy Signatures" + }, + { + "alarm": true, + "block": false, + "learn": true, + "name": "All Signatures" + } + ], + "signature-settings": { + "attackSignatureFalsePositiveMode": "disabled", + "minimumAccuracyForAutoAddedSignatures": "high", + "placeSignaturesInStaging": false, + "signatureStaging": false, + "stagingCertificationDatetime": "" + }, + "softwareVersion": "17.0.0", + "template": { + "name": "BlankTemplate" + }, + "threat-campaign-settings": { + "threatCampaignEnforcementReadinessPeriod": 1, + "threatCampaignStaging": false + }, + "type": "security" + } + } \ No newline at end of file diff --git a/docs/resources/cm_activate_instance_license.md b/docs/resources/cm_activate_instance_license.md new file mode 100644 index 0000000..1e41154 --- /dev/null +++ b/docs/resources/cm_activate_instance_license.md @@ -0,0 +1,46 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "bigipnext_cm_activate_instance_license Resource - terraform-provider-bigipnext" +subcategory: "" +description: |- + Resource used for Activate/Deactivate License for Instances on Central Manager Using JWT Token +--- + +# bigipnext_cm_activate_instance_license (Resource) + +Resource used for Activate/Deactivate License for Instances on Central Manager Using JWT Token + +## Example Usage + +```terraform +resource "bigipnext_cm_activate_instance_license" "tokenadd" { + instances = [{ + instance_address = "10.xxx.xxx.xxx" + jwt_id = "8a3dc22e-xxxx-xxxxc-xxxx-xxxxxxxx4326" + }, + { + instance_address = "10.146.194.174" + jwt_id = "8a3dc22e-xxxx-xxxxc-xxxx-xxxxxxxx4326" + } + ] +} +``` + + +## Schema + +### Required + +- `instances` (Attributes List) List of instances to activate the license (see [below for nested schema](#nestedatt--instances)) + +### Read-Only + +- `id` (String) Unique Identifier for the resource + + +### Nested Schema for `instances` + +Required: + +- `instance_address` (String) IP Address of the instance to activate the license +- `jwt_id` (String) JWT ID to be used to activate the license diff --git a/docs/resources/cm_add_jwt_token.md b/docs/resources/cm_add_jwt_token.md new file mode 100644 index 0000000..e12919e --- /dev/null +++ b/docs/resources/cm_add_jwt_token.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "bigipnext_cm_add_jwt_token Resource - terraform-provider-bigipnext" +subcategory: "" +description: |- + Resource used for add/copy JWT Token on Central Manager +--- + +# bigipnext_cm_add_jwt_token (Resource) + +Resource used for add/copy JWT Token on Central Manager + +## Example Usage + +```terraform +resource "bigipnext_cm_add_jwt_token" "tokenadd" { + token_name = "paid_test_jwt" + jwt_token = "eyJhbG" +} +``` + + +## Schema + +### Required + +- `jwt_token` (String, Sensitive) JWT token to be added on Central Manager +- `token_name` (String) Nickname to be used to add the JWT token on Central Manager + +### Read-Only + +- `id` (String) Unique Identifier for the resource +- `order_type` (String) JWT token to be added on Central Manager +- `subscription_expiry` (String) JWT token to be added on Central Manager diff --git a/docs/resources/cm_bootstrap.md b/docs/resources/cm_bootstrap.md new file mode 100644 index 0000000..d940d4b --- /dev/null +++ b/docs/resources/cm_bootstrap.md @@ -0,0 +1,61 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "bigipnext_cm_bootstrap Resource - terraform-provider-bigipnext" +subcategory: "" +description: |- + Resource used for bootstrapping Central Manager + ~> NOTE This resource does not support update and delete. When doing terraform destroy it will only remove the resource from the state +--- + +# bigipnext_cm_bootstrap (Resource) + +Resource used for bootstrapping Central Manager + +~> **NOTE** This resource does not support update and delete. When doing `terraform destroy` it will only remove the resource from the state + +## Example Usage + +```terraform +resource "bigipnext_cm_bootstrap" "name" { + run_setup = true + bootstrap_timeout = 800 + external_storage = { + storage_type = "NFS" + storage_address = "10.28.14.22" + storage_path = "/exports/backup" + cm_storage_dir = "backuppqr" + } +} +``` + + +## Schema + +### Required + +- `run_setup` (Boolean) Run setup on Central Manager + +### Optional + +- `bootstrap_timeout` (Number) Timeout for the bootstrap operation +- `external_storage` (Attributes) External storage configuration (see [below for nested schema](#nestedatt--external_storage)) + +### Read-Only + +- `bootstrap_status` (String) Status of the bootstrap operation +- `id` (String) ID of the resource + + +### Nested Schema for `external_storage` + +Required: + +- `storage_address` (String) IP Address of the external storage +- `storage_path` (String) Directory path that is mounted on the external storage server +- `storage_type` (String) Type of external storage. Supported values are NFS and SAMBA + +Optional: + +- `cm_storage_dir` (String) Folder name created on the external storage server to store Central Manager data +- `password` (String) Password to access the external storage, required if storage type is SAMBA +- `username` (String) Username to access the external storage, required if storage type is SAMBA diff --git a/examples/resources/bigipnext_cm_activate_instance_license/resource.tf b/examples/resources/bigipnext_cm_activate_instance_license/resource.tf new file mode 100644 index 0000000..c2dbf13 --- /dev/null +++ b/examples/resources/bigipnext_cm_activate_instance_license/resource.tf @@ -0,0 +1,11 @@ +resource "bigipnext_cm_activate_instance_license" "tokenadd" { + instances = [{ + instance_address = "10.xxx.xxx.xxx" + jwt_id = "8a3dc22e-xxxx-xxxxc-xxxx-xxxxxxxx4326" + }, + { + instance_address = "10.146.194.174" + jwt_id = "8a3dc22e-xxxx-xxxxc-xxxx-xxxxxxxx4326" + } + ] +} \ No newline at end of file diff --git a/examples/resources/bigipnext_cm_add_jwt_token/resource.tf b/examples/resources/bigipnext_cm_add_jwt_token/resource.tf new file mode 100644 index 0000000..159d12e --- /dev/null +++ b/examples/resources/bigipnext_cm_add_jwt_token/resource.tf @@ -0,0 +1,4 @@ +resource "bigipnext_cm_add_jwt_token" "tokenadd" { + token_name = "paid_test_jwt" + jwt_token = "eyJhbG" +} \ No newline at end of file diff --git a/examples/resources/bigipnext_cm_bootstrap/resource.tf b/examples/resources/bigipnext_cm_bootstrap/resource.tf new file mode 100644 index 0000000..d2bd6f6 --- /dev/null +++ b/examples/resources/bigipnext_cm_bootstrap/resource.tf @@ -0,0 +1,10 @@ +resource "bigipnext_cm_bootstrap" "name" { + run_setup = true + bootstrap_timeout = 800 + external_storage = { + storage_type = "NFS" + storage_address = "10.28.14.22" + storage_path = "/exports/backup" + cm_storage_dir = "backuppqr" + } +} \ No newline at end of file diff --git a/examples/resources/bigipnextcm_as3/resource.tf b/examples/resources/bigipnextcm_as3/resource.tf new file mode 100644 index 0000000..f47f3e5 --- /dev/null +++ b/examples/resources/bigipnextcm_as3/resource.tf @@ -0,0 +1,49 @@ +resource "bigipnext_cm_as3" "test2" { + as3_json = < **NOTE** This resource does not support update and delete. When doing `terraform destroy` it will only remove the resource from the state", + Attributes: map[string]schema.Attribute{ + "run_setup": schema.BoolAttribute{ + Required: true, + MarkdownDescription: "Run setup on Central Manager", + }, + "external_storage": schema.SingleNestedAttribute{ + Optional: true, + MarkdownDescription: "External storage configuration", + Attributes: map[string]schema.Attribute{ + "storage_type": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Type of external storage. Supported values are NFS and SAMBA", + Validators: []validator.String{ + stringvalidator.OneOf([]string{"NFS", "SAMBA"}...), + }, + }, + "storage_address": schema.StringAttribute{ + Required: true, + MarkdownDescription: "IP Address of the external storage", + }, + "storage_path": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Directory path that is mounted on the external storage server", + }, + "cm_storage_dir": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Folder name created on the external storage server to store Central Manager data", + }, + "username": schema.StringAttribute{ + MarkdownDescription: "Username to access the external storage, required if storage type is SAMBA", + Optional: true, + }, + "password": schema.StringAttribute{ + MarkdownDescription: "Password to access the external storage, required if storage type is SAMBA", + Optional: true, + }, + }, + }, + "bootstrap_timeout": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "Timeout for the bootstrap operation", + }, + "bootstrap_status": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Status of the bootstrap operation", + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "ID of the resource", + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + }, + } +} + +func (r *CMNextBootstrapResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.client, resp.Diagnostics = toBigipNextCMProvider(req.ProviderData) +} + +func (r *CMNextBootstrapResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var resCfg CMNextBootstrapResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + + if resp.Diagnostics.HasError() { + return + } + + if !resCfg.ExternalStorage.IsNull() { + var externalStorageModel ExternalStorage + diag := resCfg.ExternalStorage.As(ctx, &externalStorageModel, basetypes.ObjectAsOptions{}) + + if diag.HasError() { + return + } + + externalStorage := getExternalStorageData(&externalStorageModel) + res, err := r.client.AddExternalStorage(externalStorage) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to add external storage:", err.Error()) + return + } + + externalStoreResp := &bigipnextsdk.CMExternalStorageResp{} + + tflog.Info(ctx, "External storage setup response: "+res) + err = json.Unmarshal([]byte(res), externalStoreResp) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to unmarshal external storage response:", err.Error()) + return + } + + if externalStoreResp.Status.Setup != "SUCCESSFUL" { // coverage-ignore + tflog.Error(ctx, "Failed to setup external storage") + resp.Diagnostics.AddError("Failed to setup external storage", externalStoreResp.Status.FailureMessage) + return + } + + typeMap := map[string]attr.Type{ + "storage_type": types.StringType, + "storage_address": types.StringType, + "storage_path": types.StringType, + "cm_storage_dir": types.StringType, + "username": types.StringType, + "password": types.StringType, + } + valMap := map[string]attr.Value{ + "storage_type": types.StringValue(externalStoreResp.Spec.StorageType), + "storage_address": types.StringValue(externalStoreResp.Spec.StorageAddress), + "storage_path": types.StringValue(externalStoreResp.Spec.StorageSharePath), + } + + if externalStoreResp.Spec.StorageType == "SAMBA" { + valMap["username"] = types.StringValue(externalStoreResp.Spec.StorageUser.Username) + valMap["password"] = types.StringValue(externalStoreResp.Spec.StorageUser.Password) + } else { + valMap["username"] = types.StringNull() + valMap["password"] = types.StringNull() + } + if externalStoreResp.Spec.StorageShareDir != "" { + valMap["cm_storage_dir"] = types.StringValue(externalStoreResp.Spec.StorageShareDir) + } else { + if !externalStorageModel.CMStorageDir.IsNull() { + valMap["cm_storage_dir"] = externalStorageModel.CMStorageDir + } else { + valMap["cm_storage_dir"] = types.StringNull() + } + } + + resCfg.ExternalStorage, _ = types.ObjectValue(typeMap, valMap) + } + + var cmBootstrapStatus string + if resCfg.RunSetup.ValueBool() { + var timeout int64 + if resCfg.BootstrapTimeout.IsNull() { + timeout = 600 + } else { // coverage-ignore + timeout = resCfg.BootstrapTimeout.ValueInt64() + } + res, err := r.client.BootstrapCM(timeout) + + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to bootstrap Central Manager:", err.Error()) + return + } + + bootStrapResp := &bigipnextsdk.BootstrapCMResp{} + err = json.Unmarshal([]byte(res), bootStrapResp) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to unmarshal Central Manager bootstrap response:", err.Error()) + return + } + + cmBootstrapStatus = bootStrapResp.Status + + tflog.Info(ctx, "Central Manager bootstrap response: "+res) + } + + id := extractIPFromUrl(r.client.Host) + + resCfg.Id = types.StringValue(fmt.Sprintf("setup-%s", id)) + resCfg.BootstrapStatus = types.StringValue(cmBootstrapStatus) + resp.Diagnostics.Append(resp.State.Set(ctx, &resCfg)...) +} + +func (r *CMNextBootstrapResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var stateCfg *CMNextBootstrapResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + if resp.Diagnostics.HasError() { + return + } + + res, err := r.client.GetCMExternalStorage() + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to read external storage status", err.Error()) + } + externalStorageResp := &bigipnextsdk.CMExternalStorageResp{} + err = json.Unmarshal([]byte(res), externalStorageResp) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to unmarshal external storage response", err.Error()) + } + + typeMap := map[string]attr.Type{ + "storage_type": types.StringType, + "storage_address": types.StringType, + "storage_path": types.StringType, + "cm_storage_dir": types.StringType, + "username": types.StringType, + "password": types.StringType, + } + valMap := map[string]attr.Value{ + "storage_type": types.StringValue(externalStorageResp.Spec.StorageType), + "storage_address": types.StringValue(externalStorageResp.Spec.StorageAddress), + "storage_path": types.StringValue(externalStorageResp.Spec.StorageSharePath), + } + + if externalStorageResp.Spec.StorageType == "SAMBA" { + valMap["username"] = types.StringValue(externalStorageResp.Spec.StorageUser.Username) + valMap["password"] = types.StringValue(externalStorageResp.Spec.StorageUser.Password) + } else { + valMap["username"] = types.StringNull() + valMap["password"] = types.StringNull() + } + if externalStorageResp.Spec.StorageShareDir != "" { + valMap["cm_storage_dir"] = types.StringValue(externalStorageResp.Spec.StorageShareDir) + } else { + valMap["cm_storage_dir"] = types.StringNull() + } + + stateCfg.ExternalStorage, _ = types.ObjectValue(typeMap, valMap) + + bootstrap, err := r.client.GetCMBootstrap() + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to read the bootstrap status", err.Error()) + } + bootstrapResp := &bigipnextsdk.BootstrapCMResp{} + err = json.Unmarshal([]byte(bootstrap), bootstrapResp) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to unmarshal the bootstrap response", err.Error()) + } + + id := extractIPFromUrl(r.client.Host) + + stateCfg.Id = types.StringValue(fmt.Sprintf("setup-%s", id)) + stateCfg.BootstrapStatus = types.StringValue(bootstrapResp.Status) +} + +func (r *CMNextBootstrapResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + tflog.Info(ctx, "this resource does not support update operation") +} + +func (r *CMNextBootstrapResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Info(ctx, "this resource does not support delete operation, it can oly be removed from the state") + resp.State.SetAttribute(ctx, path.Root("id"), types.StringValue("")) +} + +func getExternalStorageData(plan *ExternalStorage) *bigipnextsdk.CMExternalStorage { + externalStorage := &bigipnextsdk.CMExternalStorage{} + + externalStorage.StorageType = plan.StorageType.ValueString() + externalStorage.StorageAddress = plan.StorageAddress.ValueString() + externalStorage.StorageSharePath = plan.StoragePath.ValueString() + + if plan.CMStorageDir.IsNull() { + externalStorage.StorageShareDir = "" + } else { + externalStorage.StorageShareDir = plan.CMStorageDir.ValueString() + } + + if !plan.Username.IsNull() { + externalStorage.StorageUser = &bigipnextsdk.CMExternalStorageUser{ + Username: plan.Username.ValueString(), + Password: plan.Password.ValueString(), + } + } + + return externalStorage +} diff --git a/internal/provider/cm_bootstrap_resource_test.go b/internal/provider/cm_bootstrap_resource_test.go new file mode 100644 index 0000000..aaaca44 --- /dev/null +++ b/internal/provider/cm_bootstrap_resource_test.go @@ -0,0 +1,290 @@ +package provider + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + bigipnextsdk "gitswarm.f5net.com/terraform-providers/bigipnext" +) + +func TestAccCMBootstrap(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCMBootstrapConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("bigipnext_cm_bootstrap.cm_bootstrap", "run_setup", "false"), + resource.TestCheckResourceAttr("bigipnext_cm_bootstrap.cm_bootstrap", "external_storage.storage_type", "NFS"), + resource.TestCheckResourceAttr("bigipnext_cm_bootstrap.cm_bootstrap", "external_storage.storage_address", "10.218.134.22"), + ), + }, + }, + }) +} + +func TestUnitCMBootstrap(t *testing.T) { + testAccPreUnitCheck(t) + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + mux.HandleFunc("/api/v1/system/infra/external-storage", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + payload, err := io.ReadAll(r.Body) + if err != nil && err.Error() != "EOF" { + t.Fatal(err) + } + + payloadStruct := &bigipnextsdk.CMExternalStorage{} + err = json.Unmarshal(payload, payloadStruct) + if err != nil { + t.Fatal(err) + } + + if payloadStruct.StorageAddress != "10.218.134.22" { + t.Errorf("Expected storage address to be 10.218.134.22, got %v", payloadStruct.StorageAddress) + } + if payloadStruct.StorageType != "NFS" { + t.Errorf("Expected storage type to be NFS, got %v", payloadStruct.StorageType) + } + if payloadStruct.StorageSharePath != "/exports/backup" { + t.Errorf("Expected storage path to be /exports/backup, got %v", payloadStruct.StorageSharePath) + } + + fmt.Fprint(w, ` + { + "spec": { + "storage_address": "10.218.134.22", + "storage_share_dir": "", + "storage_share_path": "/exports/backup", + "storage_type": "NFS" + }, + "status": { + "setup": "SUCCESSFUL" + } + }`) + } + if r.Method == http.MethodGet { + fmt.Fprint(w, ` + { + "spec": { + "storage_address": "10.218.134.22", + "storage_share_dir": "9fb2d367-e8e6-4c09-abd2-faaee96ebe93", + "storage_share_path": "/exports/backup", + "storage_type": "NFS" + }, + "status": { + "setup": "SUCCESSFUL" + } + }`) + } + }) + + i := 0 + bootstrapStatuses := []string{ + `{ + "created": "2024-07-15T06:31:49.322459826Z", + "status": "RUNNING", + "step": "Installing Elasticsearch", + "updated": "2024-07-15T06:40:36.083148739Z" + }`, + `{ + "created": "2024-07-15T06:31:49.322459826Z", + "status": "RUNNING", + "step": "Installing Central Manager applications", + "updated": "2024-07-15T06:40:36.083148739Z" + }`, + `{ + "created": "2024-07-15T06:31:49.322459826Z", + "status": "COMPLETED", + "step": "Done", + "updated": "2024-07-15T06:40:36.083148739Z" + }`, + `{ + "created": "2024-07-15T06:31:49.322459826Z", + "status": "COMPLETED", + "step": "Done", + "updated": "2024-07-15T06:40:36.083148739Z" + }`, + } + + mux.HandleFunc("/api/v1/system/infra/bootstrap", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + fmt.Fprint(w, ` + { + "created": "2024-07-15T06:31:49.322459826Z", + "status": "RUNNING", + "step": "Installing Kafka", + "updated": "2024-07-15T06:40:36.083148739Z" + } + `) + } + if r.Method == http.MethodGet { + fmt.Fprint(w, bootstrapStatuses[i]) + i += 1 + } + }) + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testUnitCMBootstrapConfig, + }, + }, + }) +} + +func TestUnitCMBootstrapSAMBA(t *testing.T) { + testAccPreUnitCheck(t) + defer teardown() + + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + mux.HandleFunc("/api/v1/system/infra/external-storage", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + payload, err := io.ReadAll(r.Body) + if err != nil && err.Error() != "EOF" { + t.Fatal(err) + } + + payloadStruct := &bigipnextsdk.CMExternalStorage{} + err = json.Unmarshal(payload, payloadStruct) + + if err != nil { + t.Fatal(err) + } + + if payloadStruct.StorageAddress != "10.22.33.44" { + t.Errorf("Expected storage address to be 10.22.33.44, got %v", payloadStruct.StorageAddress) + } + if payloadStruct.StorageType != "SAMBA" { + t.Errorf("Expected storage type to be SAMBA, got %v", payloadStruct.StorageType) + } + if payloadStruct.StorageSharePath != "/exports/backup" { + t.Errorf("Expected storage path to be /exports/backup, got %v", payloadStruct.StorageSharePath) + } + if payloadStruct.StorageShareDir != "backup" { + t.Errorf("Expected storage path to be backup, got %v", payloadStruct.StorageShareDir) + } + + fmt.Fprint(w, ` + { + "spec": { + "storage_address": "10.22.33.44", + "storage_share_dir": "", + "storage_share_path": "/exports/backup", + "storage_type": "SAMBA", + "storage_user": { + "username": "admin", + "password": "password" + } + }, + "status": { + "setup": "SUCCESSFUL" + } + }`) + } + if r.Method == http.MethodGet { + fmt.Fprint(w, ` + { + "spec": { + "storage_address": "10.22.33.44", + "storage_share_dir": "backup", + "storage_share_path": "/exports/backup", + "storage_type": "SAMBA", + "storage_user": { + "username": "admin", + "password": "password" + } + }, + "status": { + "setup": "SUCCESSFUL" + } + }`) + } + }) + + mux.HandleFunc("/api/v1/system/infra/bootstrap", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, ` + { + "created": "2024-07-15T06:31:49.322459826Z", + "status": "SUCCESSFUL", + "step": "Installing Kafka", + "updated": "2024-07-15T06:40:36.083148739Z" + } + `) + }) + + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testUnitCMBootstrapConfigSAMBA, + }, + }, + }) +} + +const testAccCMBootstrapConfig = ` +resource "bigipnext_cm_bootstrap" "cm_bootstrap" { + run_setup = false + external_storage = { + storage_type = "NFS" + storage_address = "10.218.134.22" + storage_path = "/exports/backup" + } +} +` + +const testUnitCMBootstrapConfig = ` +resource "bigipnext_cm_bootstrap" "cm_bootstrap" { + run_setup = true + external_storage = { + storage_type = "NFS" + storage_address = "10.218.134.22" + storage_path = "/exports/backup" + } +} +` + +const testUnitCMBootstrapConfigSAMBA = ` +resource "bigipnext_cm_bootstrap" "cm_bootstrap" { + run_setup = false + external_storage = { + storage_type = "SAMBA" + storage_address = "10.22.33.44" + storage_path = "/exports/backup" + cm_storage_dir = "backup" + username = "admin" + password = "password" + } +} +` diff --git a/internal/provider/cm_discovery_next_resource.go b/internal/provider/cm_discovery_next_resource.go index 585f5fd..ad8b8d5 100644 --- a/internal/provider/cm_discovery_next_resource.go +++ b/internal/provider/cm_discovery_next_resource.go @@ -89,7 +89,7 @@ func (r *CMDiscoveryNextResource) Configure(ctx context.Context, req resource.Co func (r *CMDiscoveryNextResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var resCfg *CMDiscoveryNextResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[CREATE] CMDiscoveryNextResource:%+v\n", resCfg.Address.ValueString())) @@ -99,7 +99,7 @@ func (r *CMDiscoveryNextResource) Create(ctx context.Context, req resource.Creat tflog.Info(ctx, fmt.Sprintf("[CREATE] Device Provider config:%+v\n", providerConfig)) respData, err := r.client.DiscoverInstance(providerConfig) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Create Certificate, got error: %s", err)) return } @@ -111,14 +111,14 @@ func (r *CMDiscoveryNextResource) Create(ctx context.Context, req resource.Creat func (r *CMDiscoveryNextResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *CMDiscoveryNextResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("Reading Instance Info : %+v", id)) deviceInfo, err := r.client.GetDeviceInfoByID(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Instance Info, got error: %s", err)) return } @@ -138,7 +138,7 @@ func (r *CMDiscoveryNextResource) Update(ctx context.Context, req resource.Updat var resCfg *CMDiscoveryNextResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[UPDATE] Updating Device Provider: %s", resCfg.Id.ValueString())) @@ -148,13 +148,13 @@ func (r *CMDiscoveryNextResource) Update(ctx context.Context, req resource.Updat func (r *CMDiscoveryNextResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *CMDiscoveryNextResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) id := stateCfg.Id.ValueString() err := r.client.DeleteDevice(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete Instance, got error: %s", err)) return } diff --git a/internal/provider/cm_ha_cluster_resource.go b/internal/provider/cm_ha_cluster_resource.go index f5c73fa..d54c6d1 100644 --- a/internal/provider/cm_ha_cluster_resource.go +++ b/internal/provider/cm_ha_cluster_resource.go @@ -115,7 +115,7 @@ func (r *NextCMHAClusterResource) Create(ctx context.Context, req resource.Creat var nodeCheck []string for _, node := range resCfg.Nodes { - if node.Fingerprint.IsNull() { + if node.Fingerprint.IsNull() { // coverage-ignore fingerprint, err := getFingerPrint(node.NodeIP.String()) if err != nil { resp.Diagnostics.AddError(fmt.Sprintf("error getting fingerprint of the node %s: ", node.NodeIP), err.Error()) @@ -137,7 +137,7 @@ func (r *NextCMHAClusterResource) Create(ctx context.Context, req resource.Creat res, err := r.client.CreateCMHACluster(nodes) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("error creating CM HA cluster", err.Error()) return } @@ -145,7 +145,7 @@ func (r *NextCMHAClusterResource) Create(ctx context.Context, req resource.Creat log.Printf("Started CM HA Cluster creation: %v", res) res2, err := r.client.CheckCMHANodesStatus(nodeCheck) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("error creating CM HA cluster", err.Error()) return } @@ -220,7 +220,7 @@ func (r *NextCMHAClusterResource) Update(ctx context.Context, req resource.Updat if !doAddOperation { continue } - if node.Fingerprint.IsNull() { + if node.Fingerprint.IsNull() { // coverage-ignore fingerprint, err := getFingerPrint(node.NodeIP.String()) if err != nil { resp.Diagnostics.AddError(fmt.Sprintf("error getting fingerprint of the node %s: ", node.NodeIP), err.Error()) @@ -241,7 +241,7 @@ func (r *NextCMHAClusterResource) Update(ctx context.Context, req resource.Updat } _, err := r.client.CreateCMHACluster(nodes) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("error updating CM HA cluster", err.Error()) return } @@ -249,7 +249,7 @@ func (r *NextCMHAClusterResource) Update(ctx context.Context, req resource.Updat } res, err := r.client.CheckCMHANodesStatus(nodeCheck) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("error reading CM HA cluster", err.Error()) return } @@ -283,7 +283,7 @@ func (r *NextCMHAClusterResource) Delete(ctx context.Context, req resource.Delet } } - if nodeCount > 1 { + if nodeCount > 1 { // coverage-ignore tflog.Error(ctx, "unable to delete all the nodes of the cluster") resp.Diagnostics.AddError("delete operation failed", "unable to delete all the nodes of the cluster") } diff --git a/internal/provider/cm_ha_cluster_resource_test.go b/internal/provider/cm_ha_cluster_resource_test.go index 49af772..9a95ad5 100644 --- a/internal/provider/cm_ha_cluster_resource_test.go +++ b/internal/provider/cm_ha_cluster_resource_test.go @@ -1,11 +1,14 @@ package provider import ( + "encoding/json" "fmt" + "net/http" "os" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "gitswarm.f5net.com/terraform-providers/bigipnext" ) func TestAccCMHAClusterTC(t *testing.T) { @@ -36,11 +39,218 @@ func TestAccCMHAClusterTC(t *testing.T) { }) } +func TestUnitCMHAClusterCreate(t *testing.T) { + testAccPreUnitCheck(t) + defer teardown() + + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + afterDeleteCall := false + + mux.HandleFunc("/api/v1/system/infra/nodes", func(w http.ResponseWriter, r *http.Request) { + + if r.Method == http.MethodPost { + payload := make([]byte, r.ContentLength) + + _, err := r.Body.Read(payload) + + if err != nil && err.Error() != "EOF" { + t.Errorf("Error reading request body: %v", err) + } + + defer r.Body.Close() + var nodes []bigipnext.CMHANodes + + err = json.Unmarshal(payload, &nodes) + if err != nil { + t.Errorf("Error unmarshalling request body: %v", err) + } + + if nodes[0].NodeAddress != "10.146.164.150" { + t.Errorf("Expected node address to be 10.146.164.150, got %s", nodes[0].NodeAddress) + } + if nodes[0].Username != "admin" { + t.Errorf("Expected username to be admin, got %s", nodes[0].Username) + } + if nodes[0].Password != "F5site02@123" { + t.Errorf("Expected password to be F5site02@123, got %s", nodes[0].Password) + } + if nodes[0].Fingerprint != "c2b2472624cd7f3a053c4f6e0bd4b322" { + t.Errorf("Expected fingerprint to be c2b2472624cd7f3a053c4f6e0bd4b322, got %s", nodes[0].Fingerprint) + } + + if nodes[1].NodeAddress != "10.146.165.89" { + t.Errorf("Expected node address to be 10.146.165.89, got %s", nodes[1].NodeAddress) + } + if nodes[1].Username != "admin" { + t.Errorf("Expected username to be admin, got %s", nodes[1].Username) + } + if nodes[1].Password != "F5site02@123" { + t.Errorf("Expected password to be F5site02@123, got %s", nodes[1].Password) + } + if nodes[1].Fingerprint != "c2b2472624cd7f3a053c4f6e0bd4b322" { + t.Errorf("Expected fingerprint to be c2b2472624cd7f3a053c4f6e0bd4b322, got %s", nodes[1].Fingerprint) + } + + postResp, _ := os.ReadFile("fixtures/cm_ha_nodes_post.json") + fmt.Fprint(w, postResp) + } + + if r.Method == http.MethodGet { + getResp, _ := os.ReadFile("fixtures/cm_ha_nodes_get.json") + fmt.Fprint(w, getResp) + } + + if r.Method == http.MethodGet && afterDeleteCall { + getAfterDeleteResp, _ := os.ReadFile("fixtures/cm_ha_nodes_get_after_delete.json") + fmt.Fprint(w, getAfterDeleteResp) + } + }) + + mux.HandleFunc("/api/v1/system/infra/nodes/central-manager-10-146-165-89", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodDelete { + afterDeleteCall = true + w.WriteHeader(http.StatusAccepted) + fmt.Fprint(w, "node central-manager-10-146-165-89 is in process of deregistration.") + } + }) + + mux.HandleFunc("/api/v1/system/infra/nodes/central-manager-10-146-164-150", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodDelete { + afterDeleteCall = true + w.WriteHeader(http.StatusAccepted) + fmt.Fprint(w, "node central-manager-10-146-164-150 is in process of deregistration.") + } + }) + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testUnitCMHAClusterConfig, + }, + }, + }) +} + +func TestUnitCMHAClusterUpdate(t *testing.T) { + testAccPreUnitCheck(t) + defer teardown() + + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + afterDeleteCall := false + updateCall := false + mux.HandleFunc("/api/v1/system/infra/nodes", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + postResp, _ := os.ReadFile("fixtures/cm_ha_nodes_post.json") + fmt.Fprint(w, postResp) + updateCall = true + } + + if r.Method == http.MethodPost && updateCall { + postResp, _ := os.ReadFile("fixtures/cm_ha_nodes_post_update.json") + fmt.Fprint(w, postResp) + } + + if r.Method == http.MethodGet { + getResp, _ := os.ReadFile("fixtures/cm_ha_nodes_get.json") + fmt.Fprint(w, getResp) + } + + if r.Method == http.MethodGet && afterDeleteCall { + getAfterDeleteResp, _ := os.ReadFile("fixtures/cm_ha_nodes_get_after_delete.json") + fmt.Fprint(w, getAfterDeleteResp) + } + }) + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testUnitCMHAClusterConfig, + Destroy: false, + }, + { + Config: testUnitCMHAClusterUpdateConfig, + Destroy: false, + }, + }, + }) +} + +func TestUnitCMHAClusterGetFingerprint(t *testing.T) { + fp1, err := getFingerPrint("8.8.8.8") + if len(fp1) != 64 { + t.Errorf("Expected fingerprint length to be 64, got %d", len(fp1)) + } + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + _, err = getFingerPrint("a.b.c.d") + if err == nil { + t.Errorf("Expected error for a bogus IP address, got nil") + } +} + +func TestUnitCMHAClusterGetServerAndAgentNodes(t *testing.T) { + nodes := []bigipnext.CMHANodesStatus{ + { + Spec: bigipnext.CMHANodeSpec{ + NodeAddress: "1.2.3.4", + NodeType: "server", + }, + }, + { + Spec: bigipnext.CMHANodeSpec{ + NodeAddress: "5.6.7.8", + NodeType: "agent", + }, + }, + } + + serverNodes, agentNodes := getServerAndAgentNodes(nodes) + if len(serverNodes) != 1 { + t.Errorf("Expected server nodes length to be 1, got %d", len(serverNodes)) + } + if len(agentNodes) != 1 { + t.Errorf("Expected agent nodes length to be 1, got %d", len(agentNodes)) + } + + if serverNodes[0] != "1.2.3.4" { + t.Errorf("Expected server node address to be 1.2.3.4, got %s", nodes[0].Spec.NodeAddress) + } + if agentNodes[0] != "5.6.7.8" { + t.Errorf("Expected agent node address to be 5.6.7.8, got %s", nodes[1].Spec.NodeAddress) + } +} + const testAccCMHAClusterConfig = ` resource "bigipnext_cm_ha_cluster" "cm_ha_2_nodes" { nodes = [ { - node_ip = "10.146.164.150" + node_ip = "10.146.164.150" username = "admin", password = "F5site02@123" } @@ -52,15 +262,59 @@ const testAccCMHAClusterUpdateConfig = ` resource "bigipnext_cm_ha_cluster" "cm_ha_2_nodes" { nodes = [ { - node_ip = "10.146.164.150" + node_ip = "10.146.164.150" username = "admin", password = "F5site02@123" }, { - node_ip = "10.146.165.89" + node_ip = "10.146.165.89" username = "admin", password = "F5site02@123" } ] } ` + +const testUnitCMHAClusterConfig = ` +resource "bigipnext_cm_ha_cluster" "cm_ha_2_nodes" { + nodes = [ + { + node_ip = "10.146.164.150" + username = "admin", + password = "F5site02@123" + fingerprint = "c2b2472624cd7f3a053c4f6e0bd4b322" + }, + { + node_ip = "10.146.165.89" + username = "admin", + password = "F5site02@123" + fingerprint = "c2b2472624cd7f3a053c4f6e0bd4b322" + } + ] +} +` + +const testUnitCMHAClusterUpdateConfig = ` +resource "bigipnext_cm_ha_cluster" "cm_ha_2_nodes" { + nodes = [ + { + node_ip = "10.146.164.150" + username = "admin", + password = "F5site02@123" + fingerprint = "c2b2472624cd7f3a053c4f6e0bd4b322" + }, + { + node_ip = "10.146.165.89" + username = "admin", + password = "F5site02@123" + fingerprint = "c2b2472624cd7f3a053c4f6e0bd4b322" + }, + { + node_ip = "12.34.56.77" + username = "admin", + password = "F5site02@123" + fingerprint = "c2b2472624cd7f3a053c4f6e0bd4b322" + } + ] +} +` diff --git a/internal/provider/cm_next_add_jwt_token_resource.go b/internal/provider/cm_next_add_jwt_token_resource.go new file mode 100644 index 0000000..10a04c8 --- /dev/null +++ b/internal/provider/cm_next_add_jwt_token_resource.go @@ -0,0 +1,174 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + bigipnextsdk "gitswarm.f5net.com/terraform-providers/bigipnext" +) + +var ( + _ resource.Resource = &CMNextJwtTokenResource{} + _ resource.ResourceWithImportState = &CMNextJwtTokenResource{} +) + +func NewCMNextJwtTokenResource() resource.Resource { + return &CMNextJwtTokenResource{} +} + +type CMNextJwtTokenResource struct { + client *bigipnextsdk.BigipNextCM +} + +type CMNextJwtTokenResourceModel struct { + TokenName types.String `tfsdk:"token_name"` + JwtToken types.String `tfsdk:"jwt_token"` + OrderType types.String `tfsdk:"order_type"` + SubscriptionExpiry types.String `tfsdk:"subscription_expiry"` + Id types.String `tfsdk:"id"` +} + +func (r *CMNextJwtTokenResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_cm_add_jwt_token" +} + +func (r *CMNextJwtTokenResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Resource used for add/copy JWT Token on Central Manager", + Attributes: map[string]schema.Attribute{ + "token_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Nickname to be used to add the JWT token on Central Manager", + }, + "jwt_token": schema.StringAttribute{ + Required: true, + Sensitive: true, + MarkdownDescription: "JWT token to be added on Central Manager", + }, + "order_type": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "JWT token to be added on Central Manager", + }, + "subscription_expiry": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "JWT token to be added on Central Manager", + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique Identifier for the resource", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *CMNextJwtTokenResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.client, resp.Diagnostics = toBigipNextCMProvider(req.ProviderData) +} + +func (r *CMNextJwtTokenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var resCfg *CMNextJwtTokenResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + if resp.Diagnostics.HasError() { // coverage-ignore + return + } + tflog.Info(ctx, fmt.Sprintf("[CREATE] CMNextJwtTokenResource:%+v\n", resCfg.TokenName.ValueString())) + + providerConfig := getCMNextJwtTokenConfig(ctx, resCfg) + respData, err := r.client.PostLicenseToken(providerConfig) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to Create Jwt token", fmt.Sprintf(", got error: %s", err)) + return + } + tflog.Info(ctx, fmt.Sprintf("[CREATE] JWT token ID :%+v\n", string(respData))) + + resCfg.Id = types.StringValue(string(respData)) + + tokenInfo, err := r.client.GetLicenseToken(string(respData)) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to Get JWT Token Info", fmt.Sprintf(", got error: %s", err)) + return + } + tflog.Info(ctx, fmt.Sprintf("JWT Token Info : %+v", tokenInfo)) + + r.NextJwtTokenResourceModeltoState(ctx, tokenInfo, resCfg) + resp.Diagnostics.Append(resp.State.Set(ctx, resCfg)...) +} + +func (r *CMNextJwtTokenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var stateCfg *CMNextJwtTokenResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + if resp.Diagnostics.HasError() { // coverage-ignore + return + } + id := stateCfg.Id.ValueString() + tflog.Info(ctx, fmt.Sprintf("JWT Token ID : %+v", id)) + + tokenInfo, err := r.client.GetLicenseToken(id) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Get JWT Token Info, got error: %s", err)) + return + } + tflog.Info(ctx, fmt.Sprintf("JWT Token Info : %+v", tokenInfo)) + + r.NextJwtTokenResourceModeltoState(ctx, tokenInfo, stateCfg) + + resp.Diagnostics.Append(resp.State.Set(ctx, &stateCfg)...) +} + +func (r *CMNextJwtTokenResource) NextJwtTokenResourceModeltoState(ctx context.Context, respData interface{}, data *CMNextJwtTokenResourceModel) { + tflog.Debug(ctx, fmt.Sprintf("respData %+v", respData)) + data.OrderType = types.StringValue(respData.(map[string]interface{})["orderType"].(string)) + data.SubscriptionExpiry = types.StringValue(respData.(map[string]interface{})["subscriptionExpiry"].(string)) + data.TokenName = types.StringValue(respData.(map[string]interface{})["nickName"].(string)) +} + +func (r *CMNextJwtTokenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var resCfg *CMNextJwtTokenResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + + if resp.Diagnostics.HasError() { // coverage-ignore + return + } + tflog.Info(ctx, "[UPDATE] Updating JWT Tokens Not Supported!!!!") + resCfg.OrderType = types.StringValue(resCfg.OrderType.ValueString()) + resCfg.SubscriptionExpiry = types.StringValue(resCfg.SubscriptionExpiry.ValueString()) + resp.Diagnostics.Append(resp.State.Set(ctx, &resCfg)...) +} + +func (r *CMNextJwtTokenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var stateCfg *CMNextJwtTokenResourceModel + if resp.Diagnostics.HasError() { // coverage-ignore + return + } + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + id := stateCfg.Id.ValueString() + + err := r.client.DeleteLicenseToken(id) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete JWT Token, got error: %s", err)) + return + } + stateCfg.Id = types.StringValue("") +} + +func (r *CMNextJwtTokenResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func getCMNextJwtTokenConfig(ctx context.Context, data *CMNextJwtTokenResourceModel) *bigipnextsdk.JWTRequestDraft { + jwtRequest := &bigipnextsdk.JWTRequestDraft{} + tflog.Info(ctx, fmt.Sprintf("jwtRequest:%+v\n", jwtRequest)) + jwtRequest.NickName = data.TokenName.ValueString() + jwtRequest.JWT = data.JwtToken.ValueString() + return jwtRequest +} diff --git a/internal/provider/cm_next_add_jwt_token_resource_test.go b/internal/provider/cm_next_add_jwt_token_resource_test.go new file mode 100644 index 0000000..ff3e2f5 --- /dev/null +++ b/internal/provider/cm_next_add_jwt_token_resource_test.go @@ -0,0 +1,327 @@ +package provider + +import ( + "fmt" + "net/http" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestUnitCMNextAddJwtTokenResourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/license/tokens", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "NewToken": { + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }, + "DuplicatesTokenValue": { + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }, + "DuplicatesTokenNickName": { + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }, + "_links": { + "self": { + "href": "string" + } + }, + "duplicateToken": { + "_links": { + "self": { + "href": "string" + } + } + }, + "duplicateShortname": { + "_links": { + "self": { + "href": "string" + } + } + } + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/license/tokens/94ff6fac-8920-492d-9545-9e837fe31a23", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }`) + }) + defer teardown() + resource.Test(t, resource.TestCase{ + // PreCheck: func() { testAccPreUnitCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testUnitCMNextAddJwtTokenResourceTC1, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + { + Config: testUnitCMNextAddJwtTokenResourceTC2, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +func TestUnitCMNextAddJwtTokenResourceTC2(t *testing.T) { + testAccPreUnitCheck(t) + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/license/tokens", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = fmt.Fprintf(w, ` + { + "message": "description explaining the error cause in short", + "help": "help to recover from the error", + "code": "LICENSING-2245", + "status": 400, + "details": "inner details of the error" + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/license/tokens/94ff6fac-8920-492d-9545-9e837fe31a23", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }`) + }) + defer teardown() + resource.Test(t, resource.TestCase{ + // PreCheck: func() { testAccPreUnitCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testUnitCMNextAddJwtTokenResourceTC1, + ExpectError: regexp.MustCompile(`Failed to Create Jwt token`), + }, + }, + }) +} + +func TestUnitCMNextAddJwtTokenResourceTC3(t *testing.T) { + testAccPreUnitCheck(t) + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/license/tokens", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "NewToken": { + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }, + "DuplicatesTokenValue": { + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }, + "DuplicatesTokenNickName": { + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "string", + "orderSubType": "string", + "subscriptionExpiry": "string" + }, + "_links": { + "self": { + "href": "string" + } + }, + "duplicateToken": { + "_links": { + "self": { + "href": "string" + } + } + }, + "duplicateShortname": { + "_links": { + "self": { + "href": "string" + } + } + } + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/license/tokens/94ff6fac-8920-492d-9545-9e837fe31a23", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "id": "94ff6fac-8920-492d-9545-9e837fe31a23", + "nickName": "paid_test_jwt", + "entitlement": "string", + "orderType": "paid", + "orderSubType": "string", + "subscriptionExpiry": "string" + }`) + }) + defer teardown() + resource.Test(t, resource.TestCase{ + // PreCheck: func() { testAccPreUnitCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testUnitCMNextAddJwtTokenResourceTC1, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + { + Config: testUnitCMNextAddJwtTokenResourceTC1Update, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +// { +// "code": 400, +// "error": { +// "code": "LICENSING-0004", +// "message": "LICENSING-0004: JWT is not verifiable: token contains an invalid number of segments", +// "details": "", +// "help": "please retry or contact admin" +// } +// } +// +// TC1: Test case when JWT token is not verifiable +func TestAccCMNextAddJwtTokenResourceTC1(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testUnitCMNextAddJwtTokenResourceTC1, + ExpectError: regexp.MustCompile(`Failed to Create Jwt token`), + }, + }, + }) +} + +// {"code":409,"error":{"DuplicatesTokenNickName":{"orderType":"","shortName":"","tokenId":"00000000-0000-0000-0000-000000000000"},"DuplicatesTokenValue":{"entitlement":"{\"compliance\":{\"digitalAssetComplianceStatus\":\"valid\",\"digitalAssetDaysRemainingInState\":0,\"digitalAssetExpiringSoon\":false,\"digitalAssetOutOfComplianceDate\":\"\",\"entitlementCheckStatus\":\"valid\",\"entitlementExpiryStatus\":\"valid\",\"telemetryStatus\":\"valid\",\"usageExceededStatus\":\"valid\"},\"documentType\":\"\",\"documentVersion\":\"\",\"digitalAsset\":{\"digitalAssetId\":\"\",\"digitalAssetName\":\"\",\"digitalAssetVersion\":\"\",\"telemetryId\":\"\"},\"entitlementMetadata\":{\"complianceEnforcements\":[\"entitlement\"],\"complianceStates\":{\"device-twin\":[\"in-grace-period\",\"in-enforcement-period\",\"non-functional\"],\"entitlement\":[\"in-grace-period\",\"in-enforcement-period\",\"non-functional\"],\"telemetry\":[\"in-grace-period\",\"in-enforcement-period\",\"non-functional\"],\"usage\":[\"in-grace-period\",\"in-enforcement-period\"]},\"enforcementBehavior\":\"visibility\",\"enforcementPeriodDays\":0,\"entitlementModel\":\"aggregated\",\"expiringSoonNotificationDays\":7,\"entitlementExpiryDate\":\"2024-12-07T00:00:00Z\",\"gracePeriodDays\":0,\"nonContactPeriodHours\":0,\"nonFunctionalPeriodDays\":14,\"orderSubType\":\"internal\",\"orderType\":\"paid\"},\"subscriptionMetadata\":{\"programName\":\"big_ip_next_internal\",\"programTypeDescription\":\"big_ip_next_internal\",\"subscriptionId\":\"A-S00019374\",\"subscriptionExpiryDate\":\"2024-12-07T00:00:00.000Z\",\"subscriptionNotifyDays\":\"\"},\"RepositoryCertificateMetadata\":{\"sslCertificate\":\"\",\"privateKey\":\"\"},\"entitledFeatures\":[{\"entitledFeatureId\":\"\",\"featureFlag\":\"UNKNOWN_set-bigip_ltm_module_8_bigip_vcpus\",\"featurePermitted\":0,\"featureRemain\":0,\"featureUnlimited\":true,\"featureUsed\":14,\"featureValueType\":\"integral\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0},{\"entitledFeatureId\":\"\",\"featureFlag\":\"UNKNOWN_set-bigip_waf_module_8_bigip_vcpus\",\"featurePermitted\":0,\"featureRemain\":0,\"featureUnlimited\":true,\"featureUsed\":14,\"featureValueType\":\"integral\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0},{\"entitledFeatureId\":\"\",\"featureFlag\":\"UNKNOWN_set-bigip_ltm_module_1_bigip_system\",\"featurePermitted\":0,\"featureRemain\":0,\"featureUnlimited\":true,\"featureUsed\":1,\"featureValueType\":\"integral\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0},{\"entitledFeatureId\":\"\",\"featureFlag\":\"UNKNOWN_set-bigip_waf_module_1_bigip_system\",\"featurePermitted\":0,\"featureRemain\":0,\"featureUnlimited\":true,\"featureUsed\":1,\"featureValueType\":\"integral\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0},{\"entitledFeatureId\":\"\",\"featureFlag\":\"UNKNOWN_set-bigip_waf_module_2_bigip_vcpus\",\"featurePermitted\":0,\"featureRemain\":0,\"featureUnlimited\":true,\"featureUsed\":2,\"featureValueType\":\"integral\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0},{\"entitledFeatureId\":\"\",\"featureFlag\":\"UNKNOWN_set-bigip_ltm_module_2_bigip_vcpus\",\"featurePermitted\":0,\"featureRemain\":0,\"featureUnlimited\":true,\"featureUsed\":2,\"featureValueType\":\"integral\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0}]}","orderSubType":"internal","orderType":"paid","shortName":"token2","tokenId":"f88665b2-2ad3-4110-a7be-e0ccdf8667ff"},"NewToken":{"orderType":"","shortName":"","tokenId":"00000000-0000-0000-0000-000000000000"}} +// TC2: Test case when JWT token is already present +func TestAccCMNextAddJwtTokenResourceTC2(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccCMNextAddJwtTokenResourceTC2, + ExpectError: regexp.MustCompile(`Failed to Create Jwt token`), + }, + }, + }) +} + +// TC3: Test case when JWT token is not verifiable +func TestAccCMNextAddJwtTokenResourceTC3(t *testing.T) { + os.Setenv("BIGIPNEXT_HOST", "10.145.64.3") + os.Setenv("BIGIPNEXT_USERNAME", "admin") + os.Setenv("BIGIPNEXT_PASSWORD", "F5site02@123") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccCMNextAddJwtTokenResourceTC2, + // ExpectError: regexp.MustCompile(`Failed to Create Jwt token`), + }, + }, + }) +} + +const testUnitCMNextAddJwtTokenResourceTC1 = ` +resource "bigipnext_cm_add_jwt_token" "tokenadd" { + token_name = "paid_test_jwt" + jwt_token = "eyJhbG" + } +` + +const testUnitCMNextAddJwtTokenResourceTC2 = ` +resource "bigipnext_cm_add_jwt_token" "tokenadd" { + token_name = "paid_test_jwt" + jwt_token = "eyJhbG" + } +` + +const testUnitCMNextAddJwtTokenResourceTC1Update = ` +resource "bigipnext_cm_add_jwt_token" "tokenadd" { + token_name = "paid_test_jwt" + jwt_token = "eyJhbGC" + } +` + +const testAccCMNextAddJwtTokenResourceTC2 = ` +resource "bigipnext_cm_add_jwt_token" "tokenadd2" { + token_name = "paid_test_jwt" + jwt_token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InYxIiwiamt1IjoiaHR0cHM6Ly9wcm9kdWN0LmFwaXMuZjUuY29tL2VlL3YxL2tleXMvandrcyJ9.eyJzdWIiOiJGTkktZGM5MTFhYjctNDlhYi00ZWUzLTkwYjItNTNmNmVmOGY5ZTc0IiwiaWF0IjoxNzA1NjIyMDY5LCJpc3MiOiJGNSBJbmMuIiwiYXVkIjoidXJuOmY1OnRlZW0iLCJqdGkiOiJlYjIyZDhjMS1iNjVjLTExZWUtODQ1MC1mM2M0OTkzMmJmOTIiLCJmNV9vcmRlcl90eXBlIjoicGFpZCIsImY1X29yZGVyX3N1YnR5cGUiOiJpbnRlcm5hbCJ9.c6V5gAVGd-Pj62krRj8740bLq0_YyuRUvtKmat-oEJdiQn10GFbHNqBH8l3x0stcdE0UldrGszVQI3CmukDceRYi1XiTQpB69EubbOpx8Pe4qc6ht7kErmkDvsLlpy6ALYhdl8j2m5_npy3HvmoDnE2jjzWQkiQeFZjdxT4Gqc05LmRsO4_RnMOkvZFECfbRU6dEhtmP1es7L2FxJaJyJ8JEL2mz9kC8XtwaoW_jS_lxq_l5brDCnXJuFmLF882xyCReCT62FvIb4P4vzN1OQzYkRFVOJeodhHy2OdckgJC6yFlFBL0LmyA2lXUpy8mFtqxQuelmQZbD-wsxrNDzJ72CkIdg1fD6MLHQpmKQDIYEMaSFkz68nfQLsoIEOKVq6UPr0Yc-4YTsmqogaF_YN4lbUn5czmhHZgBtieitwhr4uKGJskj090kWPOQGAJae0GSUelPTk03v4vTP-efKVBb1Rj4INL1R6-la41HNJi5YXwUj1yU_gMaqzTD7G3et-fbVeMG0HuJfSENVAwzdcwmivBH-C6Iaq9g9B0BP0gC1HH_L6NSceOdSKWBYRnQZg67S3C9cGyDL8Sf29zcyNKDmjdyRnISxIy0sZZ3X3svt5QVlPS9xODl1ZztefeyfnDdgT1Rlw6bpj-UMhQSqMztclYvJXfvZOt4vQ97HYU8" + } +` diff --git a/internal/provider/cm_next_backup_restore.go b/internal/provider/cm_next_backup_restore.go index 95a83f1..1ec1d99 100644 --- a/internal/provider/cm_next_backup_restore.go +++ b/internal/provider/cm_next_backup_restore.go @@ -128,20 +128,20 @@ func (r *NextCMBackupRestoreResource) Configure(ctx context.Context, req resourc } func (r *NextCMBackupRestoreResource) GetDeviceId(data *NextCMBackupRestoreResourceModel) (deviceId *string, err error) { - if data.DeviceIp.ValueString() == "" && data.DeviceHostname.ValueString() == "" { + if data.DeviceIp.ValueString() == "" && data.DeviceHostname.ValueString() == "" { // coverage-ignore return nil, fmt.Errorf("the 'device_ip' or 'device_hostname' parameter must be specified") } if data.DeviceIp.ValueString() == "" { device := data.DeviceHostname.ValueString() deviceId, err := r.client.GetDeviceIdByHostname(device) - if err != nil { + if err != nil { // coverage-ignore return nil, err } return deviceId, nil } else { device := data.DeviceIp.ValueString() deviceId, err = r.client.GetDeviceIdByIp(device) - if err != nil { + if err != nil { // coverage-ignore return nil, err } return deviceId, nil @@ -154,11 +154,11 @@ func (r *NextCMBackupRestoreResource) Create(ctx context.Context, req resource.C // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } deviceId, err := r.GetDeviceId(data) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to obtain device id, got error: %s", err)) return } @@ -167,7 +167,7 @@ func (r *NextCMBackupRestoreResource) Create(ctx context.Context, req resource.C if data.Operation.ValueString() == "backup" { mutex.Lock() respData, err := r.client.BackupTenant(deviceId, config, int(data.Timeout.ValueInt64())) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to create config backup, got error: %s", err)) return } @@ -178,7 +178,7 @@ func (r *NextCMBackupRestoreResource) Create(ctx context.Context, req resource.C if data.Operation.ValueString() == "restore" { mutex.Lock() respData, err := r.client.RestoreTenant(deviceId, config, int(data.Timeout.ValueInt64())) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to restore config backup, got error: %s", err)) return } @@ -190,7 +190,7 @@ func (r *NextCMBackupRestoreResource) Create(ctx context.Context, req resource.C resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *NextCMBackupRestoreResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *NextCMBackupRestoreResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // coverage-ignore var data *NextCMBackupRestoreResourceModel // Read Terraform plan data into the model @@ -243,12 +243,12 @@ func (r *NextCMBackupRestoreResource) Delete(ctx context.Context, req resource.D // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } err := r.client.DeleteBackupFile(data.FileName.ValueStringPointer()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete backup file, got error: %s", err)) return } @@ -260,13 +260,13 @@ func (r *NextCMBackupRestoreResource) Read(ctx context.Context, req resource.Rea // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } //respByte, err := r.client.GetTenant(data.Name.ValueString()) respByte, err := r.client.GetBackupFile(data.FileName.ValueStringPointer()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to find backup file, got error: %s", err)) return } @@ -276,7 +276,7 @@ func (r *NextCMBackupRestoreResource) Read(ctx context.Context, req resource.Rea resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *NextCMBackupRestoreResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *NextCMBackupRestoreResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // coverage-ignore resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } @@ -298,7 +298,7 @@ func getCreateBackupRestoreConfig(ctx context.Context, req resource.CreateReques return &config } -func getUpdateBackupRestoreConfig(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) *bigipnextsdk.BackupRestoreTenantRequest { +func getUpdateBackupRestoreConfig(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) *bigipnextsdk.BackupRestoreTenantRequest { // coverage-ignore var data *NextCMBackupRestoreResourceModel // Read Terraform plan data into the model diff --git a/internal/provider/cm_next_backup_restore_test.go b/internal/provider/cm_next_backup_restore_test.go new file mode 100644 index 0000000..f0db921 --- /dev/null +++ b/internal/provider/cm_next_backup_restore_test.go @@ -0,0 +1,576 @@ +package provider + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// backup with device Ip +func TestUnitNextCMBackupCreateResourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + // var getCount = 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + // Get Device info by address + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getDeviceIdByIp.json")) + }) + + // Post backup call + mux.HandleFunc("/api/device/v1/inventory/494975ee-5eba-420a-90a6-f3aeb75bbf5b/backup", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + } + }, + "path": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + }`) + }) + + // Check Status + mux.HandleFunc("/api/device/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + } + }, + "completed": "2024-07-23T08:07:45.267699Z", + "created": "2024-07-23T08:07:24.079439Z", + "failure_reason": "", + "file_name": "test", + "file_path": "storage/backups", + "id": "c41f1566-61f8-4be2-942a-9b01c687f81a", + "instance_id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "instance_name": "big-ip-next", + "run_id": "", + "state": "backupDone", + "status": "completed" + }`) + }) + + //Get/Delete call + mux.HandleFunc("/api/device/v1/backups/test.tar.gz", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, ``) + } else { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backups/test.tar.gz" + } + }, + "file_date": "2024-07-23T10:49:22Z", + "file_name": "test.tar.gz", + "file_size": 1894032, + "instance_id": "", + "instance_name": "" + }`) + } + }) + + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMBackupByIpResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +// backup with device hostname +func TestUnitNextCMBackupCreateResourceTC2(t *testing.T) { + testAccPreUnitCheck(t) + // var getCount = 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + // Get Device info by hostname + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getDeviceIdByHostname.json")) + }) + + // Post backup call + mux.HandleFunc("/api/device/v1/inventory/494975ee-5eba-420a-90a6-f3aeb75bbf5b/backup", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + } + }, + "path": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + }`) + }) + + // Check Status + mux.HandleFunc("/api/device/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + } + }, + "completed": "2024-07-23T08:07:45.267699Z", + "created": "2024-07-23T08:07:24.079439Z", + "failure_reason": "", + "file_name": "test", + "file_path": "storage/backups", + "id": "c41f1566-61f8-4be2-942a-9b01c687f81a", + "instance_id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "instance_name": "big-ip-next", + "run_id": "", + "state": "backupDone", + "status": "completed" + }`) + }) + + //Get/Delete call + mux.HandleFunc("/api/device/v1/backups/test.tar.gz", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, ``) + } else { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backups/test.tar.gz" + } + }, + "file_date": "2024-07-23T10:49:22Z", + "file_name": "test.tar.gz", + "file_size": 1894032, + "instance_id": "", + "instance_name": "" + }`) + } + }) + + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMBackupByHostnameResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +// restore with device Ip +func TestUnitNextCMBackupCreateResourceTC3(t *testing.T) { + testAccPreUnitCheck(t) + // var getCount = 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + // Get Device info by address + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getDeviceIdByIp.json")) + }) + + // Post restore call + mux.HandleFunc("/api/device/v1/inventory/494975ee-5eba-420a-90a6-f3aeb75bbf5b/restore", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d" + } + }, + "path": "/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d" + }`) + }) + + // Check Status + mux.HandleFunc("/api/device/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d" + } + }, + "completed": "2024-07-23T13:32:01.982649Z", + "created": "2024-07-23T13:30:11.312178Z", + "failure_reason": "", + "file_name": "test.tar.gz", + "id": "016bba84-2abc-4acd-8acc-0239dbc2ec2d", + "instance_id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "instance_name": "big-ip-next", + "state": "restoreDone", + "status": "completed" + }`) + }) + + //Get/Delete call + mux.HandleFunc("/api/device/v1/backups/test.tar.gz", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, ``) + } else { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backups/test.tar.gz" + } + }, + "file_date": "2024-07-23T10:49:22Z", + "file_name": "test.tar.gz", + "file_size": 1894032, + "instance_id": "", + "instance_name": "" + }`) + } + }) + + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMRestoreByIpResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +// restore with device hostname +func TestUnitNextCMBackupCreateResourceTC4(t *testing.T) { + testAccPreUnitCheck(t) + // var getCount = 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + // // Get Device info by address + // mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + // w.WriteHeader(http.StatusOK) + // _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getDeviceIdByIp.json")) + // }) + + // Get Device info by hostname + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getDeviceIdByHostname.json")) + }) + + // Post restore call + mux.HandleFunc("/api/device/v1/inventory/494975ee-5eba-420a-90a6-f3aeb75bbf5b/restore", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d" + } + }, + "path": "/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d" + }`) + }) + + // Check Status + mux.HandleFunc("/api/device/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/restore-tasks/016bba84-2abc-4acd-8acc-0239dbc2ec2d" + } + }, + "completed": "2024-07-23T13:32:01.982649Z", + "created": "2024-07-23T13:30:11.312178Z", + "failure_reason": "", + "file_name": "test.tar.gz", + "id": "016bba84-2abc-4acd-8acc-0239dbc2ec2d", + "instance_id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "instance_name": "big-ip-next", + "state": "restoreDone", + "status": "completed" + }`) + }) + + //Get/Delete call + mux.HandleFunc("/api/device/v1/backups/test.tar.gz", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, ``) + } else { + // if getCount < 2 { + // w.WriteHeader(http.StatusAccepted) + // _, _ = fmt.Fprintf(w, "") + // } else { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backups/test.tar.gz" + } + }, + "file_date": "2024-07-23T10:49:22Z", + "file_name": "test.tar.gz", + "file_size": 1894032, + "instance_id": "", + "instance_name": "" + }`) + // } + + // getCount++ + + } + }) + + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMRestoreByHostnameResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + // { + // Config: testAccNextCMWafPolicyImportUpdateResourceConfig, + // Check: resource.ComposeAggregateTestCheckFunc(), + // }, + }, + }) +} + +// update backup with device Ip +func TestUnitNextCMBackupUpdateResourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + // var getCount = 0 + var postCount = 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + // Get Device info by address + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getDeviceIdByIp.json")) + }) + + // Post backup call + mux.HandleFunc("/api/device/v1/inventory/494975ee-5eba-420a-90a6-f3aeb75bbf5b/backup", func(w http.ResponseWriter, r *http.Request) { + t.Log("Post call") + if postCount == 0 { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + } + }, + "path": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + }`) + } else { + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/0d1d9fb0-74d5-4924-ab11-85db571d26af" + } + }, + "path": "/v1/backup-tasks/0d1d9fb0-74d5-4924-ab11-85db571d26af" + }`) + } + postCount++ + }) + + // Check Status + mux.HandleFunc("/api/device/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/c41f1566-61f8-4be2-942a-9b01c687f81a" + } + }, + "completed": "2024-07-23T08:07:45.267699Z", + "created": "2024-07-23T08:07:24.079439Z", + "failure_reason": "", + "file_name": "test", + "file_path": "storage/backups", + "id": "c41f1566-61f8-4be2-942a-9b01c687f81a", + "instance_id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "instance_name": "big-ip-next", + "run_id": "", + "state": "backupDone", + "status": "completed" + }`) + }) + + mux.HandleFunc("/api/device/v1/backup-tasks/0d1d9fb0-74d5-4924-ab11-85db571d26af", func(w http.ResponseWriter, r *http.Request) { + t.Log("Checking the status of task 6af") + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backup-tasks/0d1d9fb0-74d5-4924-ab11-85db571d26af" + } + }, + "completed": "2024-07-23T08:07:45.267699Z", + "created": "2024-07-23T08:07:24.079439Z", + "failure_reason": "", + "file_name": "test", + "file_path": "storage/backups", + "id": "0d1d9fb0-74d5-4924-ab11-85db571d26af", + "instance_id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "instance_name": "big-ip-next", + "run_id": "", + "state": "backupDone", + "status": "completed" + }`) + }) + + //Get/Delete call + mux.HandleFunc("/api/device/v1/backups/test.tar.gz", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + t.Log("Inside the Delete Condition") + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, ``) + } else { + t.Log("Inside the Get Condition") + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/v1/backups/test.tar.gz" + } + }, + "file_date": "2024-07-23T10:49:22Z", + "file_name": "test.tar.gz", + "file_size": 1894032, + "instance_id": "", + "instance_name": "" + }`) + } + }) + + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMBackupByIpResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + // { + // Config: testAccNextCMBackupByIpUpdateResourceConfig, + // Check: resource.ComposeAggregateTestCheckFunc(), + // }, + }, + }) +} + +const testAccNextCMBackupByIpResourceConfig = ` +resource "bigipnext_cm_backup_restore" "sample" { + backup_password = "F5site02@123" + operation = "backup" + file_name = "test.tar.gz" + device_ip = "10.218.132.39" + } +` + +// const testAccNextCMBackupByIpUpdateResourceConfig = ` +// resource "bigipnext_cm_backup_restore" "sample" { +// backup_password = "F5site02@1234" +// operation = "backup" +// file_name = "test.tar.gz" +// device_ip = "10.218.132.39" +// } +// ` + +const testAccNextCMBackupByHostnameResourceConfig = ` +resource "bigipnext_cm_backup_restore" "sample" { + backup_password = "F5site02@123" + operation = "backup" + file_name = "test.tar.gz" + device_hostname = "big-ip-next" + } +` + +const testAccNextCMRestoreByIpResourceConfig = ` +resource "bigipnext_cm_backup_restore" "sample" { + backup_password = "F5site02@123" + operation = "restore" + file_name = "test.tar.gz" + device_ip = "10.218.132.39" + } +` + +const testAccNextCMRestoreByHostnameResourceConfig = ` +resource "bigipnext_cm_backup_restore" "sample" { + backup_password = "F5site02@123" + operation = "restore" + file_name = "test.tar.gz" + device_hostname = "big-ip-next" + } +` diff --git a/internal/provider/cm_next_ha_resource.go b/internal/provider/cm_next_ha_resource.go index 416164e..8d24e2f 100644 --- a/internal/provider/cm_next_ha_resource.go +++ b/internal/provider/cm_next_ha_resource.go @@ -133,17 +133,17 @@ func (r *NextHAResource) Configure(ctx context.Context, req resource.ConfigureRe func (r *NextHAResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var resCfg *NextHAResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } // get activeNodeID by IP activeNodeID, err := r.client.GetDeviceIdByIp(resCfg.ActiveNodeIp.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Active Device Info, got error: %s", err)) return } standbyNodeID, err := r.client.GetDeviceIdByIp(resCfg.StandbyNodeIp.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Standby Device Info, got error: %s", err)) return } @@ -152,7 +152,7 @@ func (r *NextHAResource) Create(ctx context.Context, req resource.CreateRequest, haDeployConfig := haConfig(ctx, *activeNodeID, *standbyNodeID, resCfg) tflog.Info(ctx, fmt.Sprintf("[CREATE] Deploy HA :%+v\n", haDeployConfig.ClusterName)) respData, err := r.client.PostDeviceHA(*activeNodeID, haDeployConfig, int(resCfg.Timeout.ValueInt64())) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Deploy Instance, got error: %s", err)) return } @@ -165,14 +165,14 @@ func (r *NextHAResource) Create(ctx context.Context, req resource.CreateRequest, func (r *NextHAResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextHAResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("Reading Device info for : %+v", id)) haNodeInfo, err := r.client.GetDeviceInfoByIp(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read HA Device Info, got error: %s", err)) return } @@ -180,7 +180,7 @@ func (r *NextHAResource) Read(ctx context.Context, req resource.ReadRequest, res // map[_links:map[self:map[href:/v1/inventory?filter=address+eq+%2710.146.168.20%27/23254958-db28-4d10-b42f-ff58bc16228d]] address:10.146.168.20 certificate_validated:2023-11-27T09:58:27.605586Z certificate_validation_error:tls: failed to verify certificate: x509: cannot validate certificate for 10.146.194.141 because it doesn't contain any IP SANs certificate_validity:false hostname:raviecosyshydha id:23254958-db28-4d10-b42f-ff58bc16228d mode:HA platform_name:VMware platform_type:VE port:5443 version:20.0.1-2.139.10+0.0.136] // check if mode is HA from above response map - if haNodeInfo.(map[string]interface{})["mode"] != "HA" { + if haNodeInfo.(map[string]interface{})["mode"] != "HA" { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read HA Device Info, got error: %s", err)) return } @@ -200,7 +200,7 @@ func (r *NextHAResource) Update(ctx context.Context, req resource.UpdateRequest, func (r *NextHAResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextHAResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -208,7 +208,7 @@ func (r *NextHAResource) Delete(ctx context.Context, req resource.DeleteRequest, tflog.Info(ctx, fmt.Sprintf("[DELETE] Deleting Instance from CM : %s", id)) deviceID := stateCfg.DeviceId.ValueString() err := r.client.DeleteDevice(deviceID) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete Instance, got error: %s", err)) return } diff --git a/internal/provider/cm_next_ha_resource_test.go b/internal/provider/cm_next_ha_resource_test.go new file mode 100644 index 0000000..275367c --- /dev/null +++ b/internal/provider/cm_next_ha_resource_test.go @@ -0,0 +1,143 @@ +package provider + +import ( + "fmt" + "net/http" + "net/url" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestUnitCMNextHA(t *testing.T) { + testAccPreUnitCheck(t) + defer teardown() + + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + query, _ := url.Parse(r.URL.String()) + filterQuery := query.Query().Get("filter") + + queryStr := strings.Split(filterQuery, " ") + + if strings.Contains(queryStr[2], "10.218.33.22") { + fmt.Fprint(w, ` + { + "_embedded": { + "devices": [ + { + "id": "1" + } + ] + }, + "count": 1 + } + `) + } + + if strings.Contains(queryStr[2], "10.218.33.23") { + fmt.Fprint(w, ` + { + "_embedded": { + "devices": [{ + "id": "2" + }] + }, + "count": 1 + } + `) + } + + if strings.Contains(queryStr[2], "10.218.46.27") { + fmt.Fprint(w, ` + { + "_embedded": { + "devices": [{ + "id": "2", + "mode": "HA" + }] + }, + "count": 1 + } + `) + } + }) + + mux.HandleFunc("/api/device/v1/inventory/1/ha", func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + fmt.Fprint(w, ` + { + "_links":{ + "self":{ + "href":"/v1/ha-creation-tasks/task-id-1" + } + }, + "path":"/v1/ha-creation-tasks/task-id-1" + } + `) + } + }) + + mux.HandleFunc("/api/device/v1/ha-creation-tasks/task-id-1", func(w http.ResponseWriter, r *http.Request) { + getTaskResp, _ := os.ReadFile("fixtures/cm_next_ha_task_status.json") + fmt.Fprint(w, string(getTaskResp)) + }) + + mux.HandleFunc("/api/v1/spaces/default/instances/06aea4ed-7425-4db3-a728-2574929885d9", func(w http.ResponseWriter, r *http.Request) { + + fmt.Fprint(w, ` + { + "_links":{ + "self":{ + "href":"/v1/deletion-tasks/delete-ha-task" + } + }, + "path":"/v1/deletion-tasks/delete-ha-task" + } + `) + }) + + mux.HandleFunc("/api/device/v1/deletion-tasks/delete-ha-task", func(w http.ResponseWriter, r *http.Request) { + deleteTaskStatus, _ := os.ReadFile("fixtures/cm_next_ha_delete_task_status.json") + fmt.Fprint(w, string(deleteTaskStatus)) + }) + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testUnitCMNextHAConfig, + }, + }, + }) +} + +const testUnitCMNextHAConfig = ` +resource "bigipnext_cm_next_ha" "test" { + ha_name = "testnextha" + ha_ip = "10.218.46.27" + active_node_ip = "10.218.33.22" + standby_node_ip = "10.218.33.23" + control_plane_vlan = "ha-cp-vlan" + control_plane_vlan_tag = 101 + data_plane_vlan = "ha-dp-vlan" + data_plane_vlan_tag = 102 + active_node_control_plane_ip = "10.211.44.0/8" + standby_node_control_plane_ip = "10.211.31.0/8" + active_node_data_plane_ip = "10.211.98.0/8" + standby_node_data_plane_ip = "10.211.76.0/8" +} +` diff --git a/internal/provider/cm_next_license_activate_resource.go b/internal/provider/cm_next_license_activate_resource.go new file mode 100644 index 0000000..c27fe85 --- /dev/null +++ b/internal/provider/cm_next_license_activate_resource.go @@ -0,0 +1,185 @@ +package provider + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + bigipnextsdk "gitswarm.f5net.com/terraform-providers/bigipnext" +) + +var ( + _ resource.Resource = &CMNextLicenseActivateResource{} + _ resource.ResourceWithImportState = &CMNextLicenseActivateResource{} +) + +func NewCMNextLicenseActivateResource() resource.Resource { + return &CMNextLicenseActivateResource{} +} + +type CMNextLicenseActivateResource struct { + client *bigipnextsdk.BigipNextCM +} + +type CMNextLicenseActivateResourceModel struct { + Instances []InstanceActivateModel `tfsdk:"instances"` + Id types.String `tfsdk:"id"` +} + +type InstanceActivateModel struct { + InstanceAddress types.String `tfsdk:"instance_address"` + JwtId types.String `tfsdk:"jwt_id"` +} + +func (r *CMNextLicenseActivateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_cm_activate_instance_license" +} + +func (r *CMNextLicenseActivateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Resource used for Activate/Deactivate License for Instances on Central Manager Using JWT Token", + Attributes: map[string]schema.Attribute{ + "instances": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "instance_address": schema.StringAttribute{ + Required: true, + MarkdownDescription: "IP Address of the instance to activate the license", + }, + "jwt_id": schema.StringAttribute{ + Required: true, + MarkdownDescription: "JWT ID to be used to activate the license", + }, + }, + }, + Required: true, + MarkdownDescription: "List of instances to activate the license", + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique Identifier for the resource", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *CMNextLicenseActivateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.client, resp.Diagnostics = toBigipNextCMProvider(req.ProviderData) +} + +func (r *CMNextLicenseActivateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var resCfg *CMNextLicenseActivateResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + if resp.Diagnostics.HasError() { // coverage-ignore + return + } + tflog.Info(ctx, "[CREATE] Activate License for Instances on Central Manager Using JWT Token") + providerConfig := getCMNextLicenseActivateConfig(ctx, r.client, resCfg) + respData, err := r.client.PostActivateLicense(providerConfig) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to Activate license", fmt.Sprintf("%+v", err)) + return + } + tflog.Info(ctx, fmt.Sprintf("[CREATE] License Task IDs :%+v\n", respData)) + var deviceID []string + for _, value := range providerConfig { + deviceID = append(deviceID, value.DigitalAssetId) + } + tflog.Info(ctx, fmt.Sprintf("Device ID : %+v", deviceID)) + // join all keys into a single string + deviceString := strings.Join(deviceID, ",") + resCfg.Id = types.StringValue(deviceString) + resp.Diagnostics.Append(resp.State.Set(ctx, resCfg)...) +} + +func (r *CMNextLicenseActivateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var stateCfg *CMNextLicenseActivateResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + if resp.Diagnostics.HasError() { // coverage-ignore + return + } + id := stateCfg.Id.ValueString() + tflog.Info(ctx, fmt.Sprintf("Instance IDs : %+v", id)) + deviceIDs := strings.Split(id, ",") + deactivateReq := &bigipnextsdk.LicenseDeactivaeReq{} + digitalAssetID := deviceIDs + deactivateReq.DigitalAssetIds = digitalAssetID + + licenseInfo, err := r.client.PostLicenseInfo(deactivateReq) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to get License Info", fmt.Sprintf("%+v", err)) + return + } + // get the license info by loop over map + var licenseStatus []string + for _, val := range licenseInfo.(map[string]interface{}) { + licenseStatus = append(licenseStatus, val.(map[string]interface{})["deviceLicenseStatus"].(map[string]interface{})["licenseStatus"].(string)) + tflog.Info(ctx, fmt.Sprintf("License Info : %+v", val.(map[string]interface{})["deviceLicenseStatus"].(map[string]interface{})["licenseStatus"])) + } + tflog.Info(ctx, fmt.Sprintf("Instance License Info : %+v", licenseStatus)) + // diags := resp.State.SetAttribute(ctx, path.Root("license_status"), licenseStatus) + // resp.Diagnostics.Append(diags...) + resp.Diagnostics.Append(resp.State.Set(ctx, &stateCfg)...) +} + +// func (r *CMNextLicenseActivateResource) NextJwtTokenResourceModeltoState(ctx context.Context, respData interface{}, data *CMNextLicenseActivateResourceModel) { +// tflog.Debug(ctx, fmt.Sprintf("respData %+v", respData)) +// } + +func (r *CMNextLicenseActivateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // var resCfg *CMNextLicenseActivateResourceModel + // resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + + // if resp.Diagnostics.HasError() { + // return + // } + tflog.Info(ctx, "[UPDATE] Update Call on Activating License Not Supported!!!!") + // resp.Diagnostics.Append(resp.State.Set(ctx, &resCfg)...) +} + +func (r *CMNextLicenseActivateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var stateCfg *CMNextLicenseActivateResourceModel + if resp.Diagnostics.HasError() { // coverage-ignore + return + } + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + id := stateCfg.Id.ValueString() + deviceIDs := strings.Split(id, ",") + deactivateReq := &bigipnextsdk.LicenseDeactivaeReq{} + digitalAssetID := deviceIDs + deactivateReq.DigitalAssetIds = digitalAssetID + res, err := r.client.PostDeactivateLicense(deactivateReq) + if err != nil { // coverage-ignore + resp.Diagnostics.AddError("Failed to deactivate license", fmt.Sprintf("%+v", err)) + return + } + tflog.Info(ctx, fmt.Sprintf("Deactivate License Response : %+v", res)) + stateCfg.Id = types.StringValue("") +} + +func (r *CMNextLicenseActivateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // coverage-ignore + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func getCMNextLicenseActivateConfig(ctx context.Context, p *bigipnextsdk.BigipNextCM, data *CMNextLicenseActivateResourceModel) []*bigipnextsdk.LicenseReq { + listLicenseRequest := []*bigipnextsdk.LicenseReq{} + for _, val := range data.Instances { + licenseRequest := &bigipnextsdk.LicenseReq{} + licenseRequest.JwtId = val.JwtId.ValueString() + deviceID, _ := p.GetDeviceIdByIp(val.InstanceAddress.ValueString()) + licenseRequest.DigitalAssetId = *deviceID + tflog.Info(ctx, fmt.Sprintf("licenseRequest:%+v", licenseRequest)) + listLicenseRequest = append(listLicenseRequest, licenseRequest) + } + return listLicenseRequest +} diff --git a/internal/provider/cm_next_license_activate_resource_test.go b/internal/provider/cm_next_license_activate_resource_test.go new file mode 100644 index 0000000..1cae543 --- /dev/null +++ b/internal/provider/cm_next_license_activate_resource_test.go @@ -0,0 +1,147 @@ +package provider + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestUnitCMNextLicenseActivateResourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + }) + mux.HandleFunc("/api/v1/spaces/default/instances/license/activate", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"422b0cec-03b9-4499-a26e-c88f57869637": { + "_links": { + "self": { + "href": "/license-task/41e49d68-d146-4e16-b286-7b57731fe14d" + } + }, + "accepted": true, + "deviceId": "422b0cec-03b9-4499-a26e-c88f57869637", + "reason": "", + "taskId": "41e49d68-d146-4e16-b286-7b57731fe14d"}}`) + }) + mux.HandleFunc("/api/v1/spaces/default/license/tasks", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "41e49d68-d146-4e16-b286-7b57731fe14d": { + "_links": { + "self": { + "href": "/license-task/41e49d68-d146-4e16-b286-7b57731fe14d" + } + }, + "taskExecutionStatus": { + "created": "2024-06-27T15:59:25.928845Z", + "failureReason": "", + "status": "completed", + "subStatus": "TERMINATE_ACK_VERIFICATION_COMPLETE", + "taskType": "activate" + }}}`) + }) + mux.HandleFunc("/api/v1/spaces/default/instances/license/license-info", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"3c815d06-0fe4-407d-b5b4-a380f550565d":{"_links":{"self":{"href":"/license/3c815d06-0fe4-407d-b5b4-a380f550565d/status"}},"deviceLicenseStatus":{"enabledFeatures":"[{\"entitledFeatureId\":\"\",\"featureFlag\":\"bigip_ltm_module\",\"featurePermitted\":1,\"featureRemain\":0,\"featureUnlimited\":false,\"featureUsed\":0,\"featureValueType\":\"boolean\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0},{\"entitledFeatureId\":\"\",\"featureFlag\":\"bigip_waf_module\",\"featurePermitted\":1,\"featureRemain\":0,\"featureUnlimited\":false,\"featureUsed\":0,\"featureValueType\":\"boolean\",\"uomCode\":\"\",\"uomTerm\":\"\",\"uomTermStart\":0}]","expiryDate":"2024-12-07T00:00:00Z","licenseStatus":"Active","licenseSubStatus":"ACK_VERIFICATION_COMPLETE","licenseToken":{"_links":{"self":{"href":"/token/dd50dae9-a9a7-49f0-ac30-5d6d44efbedb"}},"tokenId":"dd50dae9-a9a7-49f0-ac30-5d6d44efbedb","tokenName":"ravitoken"},"subscriptionSubType":"internal","subscriptionType":"paid"}}}`) + }) + mux.HandleFunc("/api/v1/spaces/default/instances/license/deactivate", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "3c815d06-0fe4-407d-b5b4-a380f550565d": { + "accepted": true, + "reason": "failed to create task", + "taskId": "d290f1ee-6c54-4b01-90e6-d701748f0982", + "deviceId": "3c815d06-0fe4-407d-b5b4-a380f550565d", + "_links": { + "self": { + "href": "/license-task/a01eeeaa-7cb1-4ce1-9d7e-6ea20e7693bb" + }}}}`) + }) + defer teardown() + resource.Test(t, resource.TestCase{ + // PreCheck: func() { testAccPreUnitCheck(t) }, + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testUnitCMNextLicenseActivateResourceTC1, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + { + Config: testUnitCMNextLicenseActivateResourceTC2, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +// TC1: Test case when JWT token is not verifiable +func TestAccCMNextLicenseActivateResourceTC1(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccCMNextLicenseActivateResourceTC1, + // ExpectError: regexp.MustCompile(`Failed to Create Jwt token`), + }, + }, + }) +} + +const testUnitCMNextLicenseActivateResourceTC1 = ` +resource "bigipnext_cm_activate_instance_license" "tokenadd" { + instances = [{ + instance_address = "10.10.10.10" + jwt_id="eyJhbGciOi" + }] +} +` + +const testUnitCMNextLicenseActivateResourceTC2 = ` +resource "bigipnext_cm_activate_instance_license" "tokenadd" { + instances = [{ + instance_address = "10.10.10.10" + jwt_id="eyJhbGciOi" + }] +} +` + +// jwtID := "8a3dc22e-dd51-4a5c-bb3a-cb239b904326" + +const testAccCMNextLicenseActivateResourceTC1 = ` +resource "bigipnext_cm_add_jwt_token" "tokenadd2" { + token_name = "ravitoken" + jwt_token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InYxIiwiamt1IjoiaHR0cHM6Ly9wcm9kdWN0LmFwaXMuZjUuY29tL2VlL3YxL2tleXMvandrcyJ9.eyJzdWIiOiJGTkktZGM5MTFhYjctNDlhYi00ZWUzLTkwYjItNTNmNmVmOGY5ZTc0IiwiaWF0IjoxNzA1NjIyMDY5LCJpc3MiOiJGNSBJbmMuIiwiYXVkIjoidXJuOmY1OnRlZW0iLCJqdGkiOiJlYjIyZDhjMS1iNjVjLTExZWUtODQ1MC1mM2M0OTkzMmJmOTIiLCJmNV9vcmRlcl90eXBlIjoicGFpZCIsImY1X29yZGVyX3N1YnR5cGUiOiJpbnRlcm5hbCJ9.c6V5gAVGd-Pj62krRj8740bLq0_YyuRUvtKmat-oEJdiQn10GFbHNqBH8l3x0stcdE0UldrGszVQI3CmukDceRYi1XiTQpB69EubbOpx8Pe4qc6ht7kErmkDvsLlpy6ALYhdl8j2m5_npy3HvmoDnE2jjzWQkiQeFZjdxT4Gqc05LmRsO4_RnMOkvZFECfbRU6dEhtmP1es7L2FxJaJyJ8JEL2mz9kC8XtwaoW_jS_lxq_l5brDCnXJuFmLF882xyCReCT62FvIb4P4vzN1OQzYkRFVOJeodhHy2OdckgJC6yFlFBL0LmyA2lXUpy8mFtqxQuelmQZbD-wsxrNDzJ72CkIdg1fD6MLHQpmKQDIYEMaSFkz68nfQLsoIEOKVq6UPr0Yc-4YTsmqogaF_YN4lbUn5czmhHZgBtieitwhr4uKGJskj090kWPOQGAJae0GSUelPTk03v4vTP-efKVBb1Rj4INL1R6-la41HNJi5YXwUj1yU_gMaqzTD7G3et-fbVeMG0HuJfSENVAwzdcwmivBH-C6Iaq9g9B0BP0gC1HH_L6NSceOdSKWBYRnQZg67S3C9cGyDL8Sf29zcyNKDmjdyRnISxIy0sZZ3X3svt5QVlPS9xODl1ZztefeyfnDdgT1Rlw6bpj-UMhQSqMztclYvJXfvZOt4vQ97HYU8" +} + +resource "bigipnext_cm_activate_instance_license" "tokenadd" { + instances = [{ + instance_address = "10.144.10.181" + jwt_id = bigipnext_cm_add_jwt_token.tokenadd2.id + }] +} +` + +// const testAccCMNextLicenseActivateResourceTC2 = ` +// resource "bigipnext_cm_add_jwt_token" "tokenadd2" { +// token_name = "paid_test_jwt" +// jwt_token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InYxIiwiamt1IjoiaHR0cHM6Ly9wcm9kdWN0LmFwaXMuZjUuY29tL2VlL3YxL2tleXMvandrcyJ9.eyJzdWIiOiJGTkktZGM5MTFhYjctNDlhYi00ZWUzLTkwYjItNTNmNmVmOGY5ZTc0IiwiaWF0IjoxNzA1NjIyMDY5LCJpc3MiOiJGNSBJbmMuIiwiYXVkIjoidXJuOmY1OnRlZW0iLCJqdGkiOiJlYjIyZDhjMS1iNjVjLTExZWUtODQ1MC1mM2M0OTkzMmJmOTIiLCJmNV9vcmRlcl90eXBlIjoicGFpZCIsImY1X29yZGVyX3N1YnR5cGUiOiJpbnRlcm5hbCJ9.c6V5gAVGd-Pj62krRj8740bLq0_YyuRUvtKmat-oEJdiQn10GFbHNqBH8l3x0stcdE0UldrGszVQI3CmukDceRYi1XiTQpB69EubbOpx8Pe4qc6ht7kErmkDvsLlpy6ALYhdl8j2m5_npy3HvmoDnE2jjzWQkiQeFZjdxT4Gqc05LmRsO4_RnMOkvZFECfbRU6dEhtmP1es7L2FxJaJyJ8JEL2mz9kC8XtwaoW_jS_lxq_l5brDCnXJuFmLF882xyCReCT62FvIb4P4vzN1OQzYkRFVOJeodhHy2OdckgJC6yFlFBL0LmyA2lXUpy8mFtqxQuelmQZbD-wsxrNDzJ72CkIdg1fD6MLHQpmKQDIYEMaSFkz68nfQLsoIEOKVq6UPr0Yc-4YTsmqogaF_YN4lbUn5czmhHZgBtieitwhr4uKGJskj090kWPOQGAJae0GSUelPTk03v4vTP-efKVBb1Rj4INL1R6-la41HNJi5YXwUj1yU_gMaqzTD7G3et-fbVeMG0HuJfSENVAwzdcwmivBH-C6Iaq9g9B0BP0gC1HH_L6NSceOdSKWBYRnQZg67S3C9cGyDL8Sf29zcyNKDmjdyRnISxIy0sZZ3X3svt5QVlPS9xODl1ZztefeyfnDdgT1Rlw6bpj-UMhQSqMztclYvJXfvZOt4vQ97HYU8" +// } +// ` diff --git a/internal/provider/cm_waf_policy_import_resource.go b/internal/provider/cm_waf_policy_import_resource.go index 8fad343..c47cac7 100644 --- a/internal/provider/cm_waf_policy_import_resource.go +++ b/internal/provider/cm_waf_policy_import_resource.go @@ -105,14 +105,14 @@ func (r *NextCMWAFPolicyImportResource) Create(ctx context.Context, req resource var resCfg *NextCMWAFPolicyImportResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } reqDraft := getCMWAFPolicyImportConfig(ctx, resCfg) tflog.Info(ctx, fmt.Sprintf("[CREATE] CM WAF Policy Import config : %+v\n", reqDraft)) id, err := r.client.PolicyImport(reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Error", fmt.Sprintf("Failed to Import WAF Policy, got error: %s", err)) return } @@ -124,13 +124,13 @@ func (r *NextCMWAFPolicyImportResource) Create(ctx context.Context, req resource func (r *NextCMWAFPolicyImportResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextCMWAFPolicyImportResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("[READ] Reading WAF Policy : %s", id)) wafData, err := r.client.GetWAFPolicyDetails(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Error", fmt.Sprintf("Failed to Read WAF Policy, got error: %s", err)) return } @@ -142,7 +142,7 @@ func (r *NextCMWAFPolicyImportResource) Update(ctx context.Context, req resource var resCfg *NextCMWAFPolicyImportResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[UPDATE] Updating WAF Policy : %s", resCfg.Name.ValueString())) @@ -155,7 +155,7 @@ func (r *NextCMWAFPolicyImportResource) Update(ctx context.Context, req resource tflog.Info(ctx, fmt.Sprintf("[UPDATE] CM WAF Policy Import config : %+v\n", reqDraft)) reqDraft.Override = "true" id, err := r.client.PolicyImport(reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Error", fmt.Sprintf("Failed to Import WAF Policy, got error: %s", err)) return } @@ -167,7 +167,7 @@ func (r *NextCMWAFPolicyImportResource) Update(ctx context.Context, req resource func (r *NextCMWAFPolicyImportResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextCMWAFPolicyImportResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -176,7 +176,7 @@ func (r *NextCMWAFPolicyImportResource) Delete(ctx context.Context, req resource tflog.Info(ctx, fmt.Sprintf("[DELETE] Deleting WAF Policy : %s", id)) err := r.client.DeleteWAFPolicy(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete WAF Policy, got error: %s", err)) return } @@ -201,8 +201,14 @@ func getCMWAFPolicyImportConfig(ctx context.Context, data *NextCMWAFPolicyImport func (r *NextCMWAFPolicyImportResource) WafPolicyModeltoState(ctx context.Context, respData interface{}, data *NextCMWAFPolicyImportResourceModel) { tflog.Info(ctx, fmt.Sprintf("WafPolicyModeltoState \t name: %+v", respData.(map[string]interface{})["name"])) data.Name = types.StringValue(respData.(map[string]interface{})["name"].(string)) - description, ok := respData.(map[string]interface{})["description"] + declaration, ok := respData.(map[string]interface{})["declaration"] if ok { - data.Description = types.StringValue(description.(string)) + policy, ok := declaration.(map[string]interface{})["policy"] + if ok { + description, ok := policy.(map[string]interface{})["description"] + if ok { + data.Description = types.StringValue(description.(string)) + } + } } } diff --git a/internal/provider/cm_waf_policy_import_resource_test.go b/internal/provider/cm_waf_policy_import_resource_test.go new file mode 100644 index 0000000..5c131c9 --- /dev/null +++ b/internal/provider/cm_waf_policy_import_resource_test.go @@ -0,0 +1,155 @@ +package provider + +import ( + "fmt" + "net/http" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccNextCMWafPolicyImportCreateTC1Resource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMWafPolicyImportResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("bigipnext_cm_waf_policy_import.sample", "name", "new_waf_policy"), + resource.TestCheckResourceAttr("bigipnext_cm_waf_policy_import.sample", "description", "new_waf_policy desc"), + ), + }, + }, + }) +} + +func TestAccNextCMWafPolicyImportCreateTC2Resource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMWafPolicyImportResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("bigipnext_cm_waf_policy_import.sample", "name", "new_waf_policy"), + resource.TestCheckResourceAttr("bigipnext_cm_waf_policy_import.sample", "description", "new_waf_policy desc"), + ), + }, + { + Config: testAccNextCMWafPolicyImportUpdateResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("bigipnext_cm_waf_policy_import.sample", "name", "new_waf_policy"), + resource.TestCheckResourceAttr("bigipnext_cm_waf_policy_import.sample", "description", "new_waf_policy desc updated"), + ), + }, + }, + }) +} + +func TestUnitNextCMWafPolicyImportCreateResourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + var getCount = 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + + // Post call + mux.HandleFunc("/api/waf/v1/tasks/policy-import", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/api/waf/v1/tasks/policy-import/1a4453fe-b37a-4212-a813-a3d2f789dad1" + } + }, + "path": "/v1/tasks/policy-import/1a4453fe-b37a-4212-a813-a3d2f789dad1" + }`) + }) + + // Check Status + mux.HandleFunc("/api/waf/v1/tasks/policy-import/1a4453fe-b37a-4212-a813-a3d2f789dad1", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "_links": { + "self": { + "href": "/api/waf/v1/tasks/policy-import/1a4453fe-b37a-4212-a813-a3d2f789dad1" + } + }, + "completed": "2024-07-17T11:59:25.7393Z", + "created": "2024-07-17T11:59:21.058179Z", + "failure_reason": "", + "id": "1a4453fe-b37a-4212-a813-a3d2f789dad1", + "policy_id": "1a4453fe-b37a-4212-a813-a3d2f789dad1", + "policy_name": "new_waf_policy", + "state": "updatingTaskDataTable", + "status": "completed", + "warnings": [] + }`) + }) + + // Get/Delete call + mux.HandleFunc("/api/v1/spaces/default/security/waf-policies/1a4453fe-b37a-4212-a813-a3d2f789dad1", func(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, ``) + } else { + if getCount < 2 { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getWaf.json")) + } else { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "%s", loadFixtureString("./fixtures/getWafUpdated.json")) + } + + getCount++ + + } + }) + + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMWafPolicyImportResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + { + Config: testAccNextCMWafPolicyImportUpdateResourceConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +var dir, _ = os.Getwd() + +var testAccNextCMWafPolicyImportResourceConfig = ` +resource "bigipnext_cm_waf_policy_import" "sample" { + name = "new_waf_policy" + description = "new_waf_policy desc" + file_path = "` + dir + `/../../configs/testwaf.json" + file_md5 = md5(file("` + dir + `/../../configs/testwaf.json")) + }` + +var testAccNextCMWafPolicyImportUpdateResourceConfig = ` +resource "bigipnext_cm_waf_policy_import" "sample" { + name = "new_waf_policy" + description = "new_waf_policy desc updated" + file_path = "` + dir + `/../../configs/testwaf.json" + file_md5 = md5(file("` + dir + `/../../configs/testwaf.json")) + } +` diff --git a/internal/provider/cm_waf_policy_resource.go b/internal/provider/cm_waf_policy_resource.go index 1c665a9..2af5448 100644 --- a/internal/provider/cm_waf_policy_resource.go +++ b/internal/provider/cm_waf_policy_resource.go @@ -192,7 +192,7 @@ func (r *NextCMWAFPolicyResource) Create(ctx context.Context, req resource.Creat var resCfg *NextCMWAFPolicyResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[CREATE] NextCMWAFPolicyResource:%+v\n", resCfg)) @@ -200,7 +200,7 @@ func (r *NextCMWAFPolicyResource) Create(ctx context.Context, req resource.Creat tflog.Info(ctx, fmt.Sprintf("[CREATE] WAF Policy config :%+v\n", reqDraft)) id, err := r.client.PostWAFPolicy("POST", reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("WAF Policy Error", fmt.Sprintf("Failed to Create WAF Policy, got error: %s", err)) return } @@ -249,13 +249,13 @@ func (r *NextCMWAFPolicyResource) Create(ctx context.Context, req resource.Creat func (r *NextCMWAFPolicyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextCMWAFPolicyResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("[READ] Reading WAF Policy : %s", id)) wafData, err := r.client.GetWAFPolicyDetails(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Error", fmt.Sprintf("Failed to Read WAF Policy, got error: %s", err)) return } @@ -268,7 +268,7 @@ func (r *NextCMWAFPolicyResource) Update(ctx context.Context, req resource.Updat var resCfg *NextCMWAFPolicyResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[UPDATE] Updating WAF Policy : %s", resCfg.Name.ValueString())) @@ -280,7 +280,7 @@ func (r *NextCMWAFPolicyResource) Update(ctx context.Context, req resource.Updat tflog.Info(ctx, fmt.Sprintf("[UPDATE] :%+v\n", reqDraft)) id, err := r.client.PostWAFPolicy("PUT", reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Update WAF Policy, got error: %s", err)) return } @@ -294,7 +294,7 @@ func (r *NextCMWAFPolicyResource) Update(ctx context.Context, req resource.Updat func (r *NextCMWAFPolicyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextCMWAFPolicyResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -303,7 +303,7 @@ func (r *NextCMWAFPolicyResource) Delete(ctx context.Context, req resource.Delet tflog.Info(ctx, fmt.Sprintf("[DELETE] Deleting WAF Policy : %s", id)) err := r.client.DeleteWAFPolicy(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete WAF Policy, got error: %s", err)) return } @@ -311,7 +311,7 @@ func (r *NextCMWAFPolicyResource) Delete(ctx context.Context, req resource.Delet stateCfg.Id = types.StringValue("") } -func (r *NextCMWAFPolicyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *NextCMWAFPolicyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // coverage-ignore // resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } @@ -337,7 +337,7 @@ func getCMWAFPolicyRequestDraft(ctx context.Context, data *NextCMWAFPolicyResour botdefenseModel.Enabled = types.BoolValue(true) if !data.BotDefense.IsNull() && !data.BotDefense.IsUnknown() { diag := data.BotDefense.As(ctx, &botdefenseModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore tflog.Error(ctx, fmt.Sprintf("[getCMWAFPolicyRequestDraft] diag Error: %+v", diag.Errors())) } } @@ -347,7 +347,7 @@ func getCMWAFPolicyRequestDraft(ctx context.Context, data *NextCMWAFPolicyResour ipintelligenceModel.Enabled = types.BoolValue(true) if !data.IpIntelligence.IsNull() && !data.IpIntelligence.IsUnknown() { diag := data.IpIntelligence.As(ctx, &ipintelligenceModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore tflog.Error(ctx, fmt.Sprintf("[getCMWAFPolicyRequestDraft] diag Error: %+v", diag.Errors())) } } @@ -357,7 +357,7 @@ func getCMWAFPolicyRequestDraft(ctx context.Context, data *NextCMWAFPolicyResour dosprotectionModel.Enabled = types.BoolValue(false) if !data.DosProtection.IsNull() && !data.DosProtection.IsUnknown() { diag := data.DosProtection.As(ctx, &dosprotectionModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore tflog.Error(ctx, fmt.Sprintf("[getCMWAFPolicyRequestDraft] diag Error: %+v", diag.Errors())) } } @@ -367,7 +367,7 @@ func getCMWAFPolicyRequestDraft(ctx context.Context, data *NextCMWAFPolicyResour blockingsettingdModel.Enabled = types.BoolValue(true) if !data.BlockingSettings.IsNull() && !data.BlockingSettings.IsUnknown() { diag := data.BlockingSettings.As(ctx, &blockingsettingdModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore tflog.Error(ctx, fmt.Sprintf("[getCMWAFPolicyRequestDraft] diag Error: %+v", diag.Errors())) } } @@ -398,7 +398,14 @@ func (r *NextCMWAFPolicyResource) WafPolicyModeltoState(ctx context.Context, res if ok { data.Description = types.StringValue(description.(string)) } - data.Tags, _ = types.ListValueFrom(ctx, types.StringType, respData.(map[string]interface{})["tags"]) + tags := respData.(map[string]interface{})["tags"] + if tags != nil { + if _, ok := tags.([]interface{}); ok { + data.Tags, _ = types.ListValueFrom(ctx, types.StringType, tags) + } + } else { + data.Tags = types.ListNull(types.StringType) + } data.EnforecementMode = types.StringValue(respData.(map[string]interface{})["enforcement_mode"].(string)) data.ApplicationLanguage = types.StringValue(respData.(map[string]interface{})["application_language"].(string)) data.Id = types.StringValue(respData.(map[string]interface{})["id"].(string)) diff --git a/internal/provider/cm_waf_report_resource.go b/internal/provider/cm_waf_report_resource.go index 3385d85..d2085d6 100644 --- a/internal/provider/cm_waf_report_resource.go +++ b/internal/provider/cm_waf_report_resource.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "regexp" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -13,7 +15,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" bigipnextsdk "gitswarm.f5net.com/terraform-providers/bigipnext" - "regexp" ) var ( @@ -160,7 +161,7 @@ func (r *NextCMWAFReportResource) Create(ctx context.Context, req resource.Creat var resCfg *NextCMWAFReportResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[CREATE] NextCMWAFReportResource:%+v\n", resCfg.Name.ValueString())) @@ -171,7 +172,7 @@ func (r *NextCMWAFReportResource) Create(ctx context.Context, req resource.Creat tflog.Info(ctx, fmt.Sprintf("[CREATE] :%+v\n", reqDraft)) id, created_by, user_defined, err := r.client.PostWAFReport("POST", reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("WAF Security Report Error", fmt.Sprintf("Failed to Create WAF Security Report, got error: %s", err)) return } @@ -187,13 +188,13 @@ func (r *NextCMWAFReportResource) Create(ctx context.Context, req resource.Creat func (r *NextCMWAFReportResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextCMWAFReportResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("[READ] Reading WAF Security Report : %s", id)) wafData, err := r.client.GetWAFReportDetails(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Error", fmt.Sprintf("Failed to Read WAF Security Report, got error: %s", err)) return } @@ -206,7 +207,7 @@ func (r *NextCMWAFReportResource) Update(ctx context.Context, req resource.Updat var resCfg *NextCMWAFReportResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[UPDATE] Updating WAF Security Report: %s", resCfg.Name.ValueString())) @@ -218,7 +219,7 @@ func (r *NextCMWAFReportResource) Update(ctx context.Context, req resource.Updat tflog.Info(ctx, fmt.Sprintf("[UPDATE] :%+v\n", reqDraft)) id, created_by, user_defined, err := r.client.PostWAFReport("PUT", reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Update WAF Security Report, got error: %s", err)) return } @@ -234,7 +235,7 @@ func (r *NextCMWAFReportResource) Update(ctx context.Context, req resource.Updat func (r *NextCMWAFReportResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextCMWAFReportResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -243,7 +244,7 @@ func (r *NextCMWAFReportResource) Delete(ctx context.Context, req resource.Delet tflog.Info(ctx, fmt.Sprintf("[DELETE] Deleting WAF Security Report : %s", id)) err := r.client.DeleteWAFReport(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete WAF Security Report, got error: %s", err)) return } @@ -251,7 +252,7 @@ func (r *NextCMWAFReportResource) Delete(ctx context.Context, req resource.Delet stateCfg.Id = types.StringValue("") } -func (r *NextCMWAFReportResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *NextCMWAFReportResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // coverage-ignore // resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } diff --git a/internal/provider/device_inventory_data_source.go b/internal/provider/device_inventory_data_source.go index 6da7e3c..fe33a17 100644 --- a/internal/provider/device_inventory_data_source.go +++ b/internal/provider/device_inventory_data_source.go @@ -65,12 +65,12 @@ func (d *DeviceInventorySource) Read(ctx context.Context, req datasource.ReadReq // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } deviceInventory, err := d.client.GetDeviceInventory() - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read Device inventory, got error: %s", err)) return } diff --git a/internal/provider/device_inventory_data_source_test.go b/internal/provider/device_inventory_data_source_test.go new file mode 100644 index 0000000..c2aff58 --- /dev/null +++ b/internal/provider/device_inventory_data_source_test.go @@ -0,0 +1,53 @@ +package provider + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestUnitNextCMDeviceInventoryDatasourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + }) + // mux.HandleFunc("/api/v1/spaces/default/providers/f5os/f05d420e-781b-409e-a17a-3c321371b3ea", func(w http.ResponseWriter, r *http.Request) { + // w.WriteHeader(http.StatusOK) + // count++ + // t.Logf("\n#####################count: %d\n", count) + // if count >= 4 { + // _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/providers/f5os/f05d420e-781b-409e-a17a-3c321371b3ea"}},"connection":{"authentication":{"type":"basic","username":"admin"},"host":"10.14.10.14:8888"},"id":"f05d420e-781b-409e-a17a-3c321371b3ea","name":"tfnext-cm-rseries01","type":"RSERIES"}`) + // } else { + // _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/providers/f5os/f05d420e-781b-409e-a17a-3c321371b3ea"}},"connection":{"authentication":{"type":"basic","username":"admin"},"host":"10.14.10.14:443"},"id":"f05d420e-781b-409e-a17a-3c321371b3ea","name":"tfnext-cm-rseries01","type":"RSERIES"}`) + // } + // }) + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMDeviceInventoryDarasourceTC1Config, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +const testAccNextCMDeviceInventoryDarasourceTC1Config = ` +data "bigipnext_cm_device_inventory" "test" {} +` diff --git a/internal/provider/device_provider_resource.go b/internal/provider/device_provider_resource.go index 43b549b..3623b5c 100644 --- a/internal/provider/device_provider_resource.go +++ b/internal/provider/device_provider_resource.go @@ -14,8 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" bigipnextsdk "gitswarm.f5net.com/terraform-providers/bigipnext" - // "strings" - // "sync" ) var ( @@ -90,7 +88,7 @@ func (r *NextCMDeviceProviderResource) Configure(ctx context.Context, req resour func (r *NextCMDeviceProviderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var resCfg *NextCMDeviceProviderResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[CREATE] NextCMDeviceProviderResource:%+v\n", resCfg.Name.ValueString())) @@ -100,7 +98,7 @@ func (r *NextCMDeviceProviderResource) Create(ctx context.Context, req resource. tflog.Info(ctx, fmt.Sprintf("[CREATE] Device Provider config:%+v\n", providerConfig)) respData, err := r.client.PostDeviceProvider(providerConfig) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Adding Provider failed with: %s", err)) return } @@ -116,14 +114,14 @@ func (r *NextCMDeviceProviderResource) Create(ctx context.Context, req resource. func (r *NextCMDeviceProviderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextCMDeviceProviderResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("Reading Device Provider : %+v", stateCfg.Name.ValueString())) deviceProvider, err := r.client.GetDeviceProvider(id, stateCfg.Type.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Device Provider, got error: %s", err)) return } @@ -144,7 +142,7 @@ func (r *NextCMDeviceProviderResource) Update(ctx context.Context, req resource. var resCfg *NextCMDeviceProviderResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[UPDATE] Updating Device Provider: %s", resCfg.Name.ValueString())) @@ -154,7 +152,7 @@ func (r *NextCMDeviceProviderResource) Update(ctx context.Context, req resource. respData, err := r.client.UpdateDeviceProvider(resCfg.Id.ValueString(), providerConfig) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Update Device provider, got error: %s", err)) return } @@ -165,7 +163,7 @@ func (r *NextCMDeviceProviderResource) Update(ctx context.Context, req resource. func (r *NextCMDeviceProviderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextCMDeviceProviderResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -173,14 +171,14 @@ func (r *NextCMDeviceProviderResource) Delete(ctx context.Context, req resource. tflog.Info(ctx, fmt.Sprintf("[DELETE] Deleting Device Provider : %s", id)) _, err := r.client.DeleteDeviceProvider(id, stateCfg.Type.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete Device Provider, got error: %s", err)) return } stateCfg.Id = types.StringValue("") } -func (r *NextCMDeviceProviderResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *NextCMDeviceProviderResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // coverage-ignore resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } diff --git a/internal/provider/device_provider_resource_test.go b/internal/provider/device_provider_resource_test.go index c313691..e17cd81 100644 --- a/internal/provider/device_provider_resource_test.go +++ b/internal/provider/device_provider_resource_test.go @@ -1,11 +1,65 @@ package provider import ( + "fmt" + "net/http" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +// {\"name\":\"tfnext-cm-vpshere01\",\"type\":\"VSPHERE\",\"connection\":{\"host\":\"mbip-70-vcenter.pdsea.f5net.com\",\"authentication\":{\"type\":\"basic\",\"username\":\"r.chinthalapalli@f5.com\",\"password\":\"TrisRav@2024\"}}} + +// {"_links":{"self":{"href":"/api/v1/spaces/default/providers/vsphere/9527cb70-182e-4185-a2f8-b8e6d898d379"}},"connection":{"authentication":{"type":"basic","username":"r.chinthalapalli@f5.com"},"host":"mbip-70-vcenter.pdsea.f5net.com"},"id":"9527cb70-182e-4185-a2f8-b8e6d898d379","name":"tfnext-cm-vpshere01","type":"VSPHERE"} + +// {"_links":{"self":{"href":"/api/v1/spaces/default/providers/vsphere/9527cb70-182e-4185-a2f8-b8e6d898d379"}},"connection":{"authentication":{"type":"basic","username":"r.chinthalapalli@f5.com"},"host":"mbip-70-vcenter.pdsea.f5net.com"},"id":"9527cb70-182e-4185-a2f8-b8e6d898d379","name":"tfnext-cm-vpshere01","type":"VSPHERE"} + +func TestUnitNextCMDeviceProviderResourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + count := 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/providers/f5os", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/providers/f5os/f05d420e-781b-409e-a17a-3c321371b3ea"}},"connection":{"authentication":{"type":"basic","username":"admin"},"host":"10.14.10.14:443"},"id":"f05d420e-781b-409e-a17a-3c321371b3ea","name":"tfnext-cm-rseries01","type":"RSERIES"}`) + }) + mux.HandleFunc("/api/v1/spaces/default/providers/f5os/f05d420e-781b-409e-a17a-3c321371b3ea", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + count++ + t.Logf("\n#####################count: %d\n", count) + if count >= 4 { + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/providers/f5os/f05d420e-781b-409e-a17a-3c321371b3ea"}},"connection":{"authentication":{"type":"basic","username":"admin"},"host":"10.14.10.14:8888"},"id":"f05d420e-781b-409e-a17a-3c321371b3ea","name":"tfnext-cm-rseries01","type":"RSERIES"}`) + } else { + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/providers/f5os/f05d420e-781b-409e-a17a-3c321371b3ea"}},"connection":{"authentication":{"type":"basic","username":"admin"},"host":"10.14.10.14:443"},"id":"f05d420e-781b-409e-a17a-3c321371b3ea","name":"tfnext-cm-rseries01","type":"RSERIES"}`) + } + }) + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextCMDeviceProviderResourceTC1Config, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + { + Config: testAccNextCMDeviceProviderResourceTC1UpdateConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + ExpectNonEmptyPlan: false, + }, + }, + }) +} + func TestAccNextCMDeviceProviderResourceTC1(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -44,10 +98,19 @@ func TestAccNextCMDeviceProviderResourceTC2(t *testing.T) { const testAccNextCMDeviceProviderResourceTC1Config = ` resource "bigipnext_cm_provider" "rseries" { - name = "testrseriesprovvm01" - address = "10.14.1.80:443" + name = "tfnext-cm-rseries01" + address = "10.14.10.14:443" + type = "RSERIES" + username = "admin" + password = "xxxxxxxx" +} +` +const testAccNextCMDeviceProviderResourceTC1UpdateConfig = ` +resource "bigipnext_cm_provider" "rseries" { + name = "tfnext-cm-rseries01" + address = "10.14.10.14:8888" type = "RSERIES" - username = "xxxxxx" + username = "admin" password = "xxxxxxxx" } ` diff --git a/internal/provider/fast_http_resource.go b/internal/provider/fast_http_resource.go new file mode 100644 index 0000000..296ba01 --- /dev/null +++ b/internal/provider/fast_http_resource.go @@ -0,0 +1,247 @@ +//go:build !test + +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + bigipnextsdk "gitswarm.f5net.com/terraform-providers/bigipnext" +) + +var ( + _ resource.Resource = &NextCMFastHttpResource{} + _ resource.ResourceWithImportState = &NextCMFastHttpResource{} +) + +func NewNextCMFastHttpResource() resource.Resource { + return &NextCMFastHttpResource{} +} + +type NextCMFastHttpResource struct { + client *bigipnextsdk.BigipNextCM +} +type NextCMFastHttpResourceModel struct { + Name types.String `tfsdk:"name"` + ApplicationDescription types.String `tfsdk:"application_description"` + ApplicationName types.String `tfsdk:"application_name"` + Pools types.List `tfsdk:"pools"` + Virtuals types.List `tfsdk:"virtuals"` + SetName types.String `tfsdk:"set_name"` + TemplateName types.String `tfsdk:"template_name"` + TenantName types.String `tfsdk:"tenant_name"` + AllowOverwrite types.Bool `tfsdk:"allow_overwrite"` + Id types.String `tfsdk:"id"` +} + +type NextCMFastHttpPoolModel struct { + LoadBalancingMode types.String `tfsdk:"load_balancing_mode"` + MonitorType types.List `tfsdk:"monitor_type"` + PoolName types.String `tfsdk:"pool_name"` + ServicePort types.Int64 `tfsdk:"service_port"` +} + +// pool_name +type NextCMFastHttpVirtualModel struct { + PoolName types.String `tfsdk:"pool_name"` + VirtualName types.String `tfsdk:"virtual_name"` + VirtualPort types.Int64 `tfsdk:"virtual_port"` +} + +func (r *NextCMFastHttpResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_cm_fast_http" +} + +func (r *NextCMFastHttpResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Resource used to manage(CRUD) AS3 declarations using BIG-IP Next CM onto target BIG-IP Next", + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Application", + }, + "application_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Application", + }, + "tenant_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Tenant", + }, + "application_description": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Description of the Application", + }, + "pools": schema.ListNestedAttribute{ + Optional: true, + MarkdownDescription: "List of Pools", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "load_balancing_mode": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Load Balancing Mode", + }, + "monitor_type": schema.ListAttribute{ + Required: true, + ElementType: types.StringType, + MarkdownDescription: "Monitor Type", + }, + "pool_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Pool", + }, + "service_port": schema.Int64Attribute{ + Required: true, + MarkdownDescription: "Service Port", + }, + }, + }, + }, + "virtuals": schema.ListNestedAttribute{ + Optional: true, + MarkdownDescription: "List of Virtuals", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "pool_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Pool", + }, + "virtual_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Virtual", + }, + "virtual_port": schema.Int64Attribute{ + Required: true, + MarkdownDescription: "Virtual Port", + }, + }, + }, + }, + "set_name": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Name of the AS3 Set", + Default: stringdefault.StaticString("Examples"), + }, + "template_name": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Name of the AS3 Template", + Default: stringdefault.StaticString("http"), + }, + "allow_overwrite": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Allow Overwrite", + Default: booldefault.StaticBool(false), + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique Identifier for the resource", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *NextCMFastHttpResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.client, resp.Diagnostics = toBigipNextCMProvider(req.ProviderData) +} + +func (r *NextCMFastHttpResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var resCfg *NextCMFastHttpResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, fmt.Sprintf("[CREATE] NextCMFastHttpResource:%+v\n", resCfg.Name.ValueString())) + reqDraft := getFastRequestDraft(ctx, resCfg) + + tflog.Info(ctx, fmt.Sprintf("[CREATE] Https:%+v\n", reqDraft)) + + draftID, err := r.client.PostFastApplicationDraft(reqDraft) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Create FAST Draft config, got error: %s", err)) + return + } + tflog.Info(ctx, fmt.Sprintf("[CREATE] draftID:%+v\n", draftID)) + resp.Diagnostics.Append(resp.State.Set(ctx, resCfg)...) +} + +func (r *NextCMFastHttpResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var stateCfg *NextCMFastHttpResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &stateCfg)...) +} + +func (r *NextCMFastHttpResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var resCfg *NextCMFastHttpResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &resCfg)...) +} + +func (r *NextCMFastHttpResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var stateCfg *NextCMFastHttpResourceModel + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + stateCfg.Id = types.StringValue("") +} + +func (r *NextCMFastHttpResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func getFastRequestDraft(ctx context.Context, data *NextCMFastHttpResourceModel) *bigipnextsdk.FastRequestDraft { + var fastReqDraft bigipnextsdk.FastRequestDraft + fastReqDraft.Name = data.Name.ValueString() + fastReqDraft.Parameters.ApplicationDescription = data.ApplicationDescription.ValueString() + fastReqDraft.Parameters.ApplicationName = data.ApplicationName.ValueString() + fastReqDraft.SetName = data.SetName.ValueString() + fastReqDraft.TemplateName = data.TemplateName.ValueString() + fastReqDraft.TenantName = data.TenantName.ValueString() + fastReqDraft.AllowOverwrite = data.AllowOverwrite.ValueBool() + elements := make([]types.Object, 0, len(data.Pools.Elements())) + data.Pools.ElementsAs(ctx, &elements, false) + for _, element := range elements { + var fastPool bigipnextsdk.FastPool + var objectModel NextCMFastHttpPoolModel + element.As(ctx, &objectModel, basetypes.ObjectAsOptions{}) + fastPool.LoadBalancingMode = objectModel.LoadBalancingMode.ValueString() + objectModel.MonitorType.ElementsAs(ctx, &fastPool.MonitorType, false) + fastPool.PoolName = objectModel.PoolName.ValueString() + fastPool.ServicePort = int(objectModel.ServicePort.ValueInt64()) + fastReqDraft.Parameters.Pools = append(fastReqDraft.Parameters.Pools, fastPool) + } + vsLists := make([]types.Object, 0, len(data.Virtuals.Elements())) + data.Virtuals.ElementsAs(ctx, &vsLists, false) + for _, element := range vsLists { + var fastVS bigipnextsdk.VirtualServer + var objectModel NextCMFastHttpVirtualModel + element.As(ctx, &objectModel, basetypes.ObjectAsOptions{}) + fastVS.VirtualName = objectModel.VirtualName.ValueString() + fastVS.VirtualPort = int(objectModel.VirtualPort.ValueInt64()) + fastReqDraft.Parameters.Virtuals = append(fastReqDraft.Parameters.Virtuals, fastVS) + } + tflog.Info(ctx, fmt.Sprintf("fastReqDraft:%+v\n", fastReqDraft)) + return &fastReqDraft +} diff --git a/internal/provider/fast_template_resource.go b/internal/provider/fast_template_resource.go new file mode 100644 index 0000000..800bc24 --- /dev/null +++ b/internal/provider/fast_template_resource.go @@ -0,0 +1,196 @@ +//go:build !test + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + bigipnextsdk "gitswarm.f5net.com/terraform-providers/bigipnext" +) + +var ( + _ resource.Resource = &NextCMFastTemplateResource{} + _ resource.ResourceWithImportState = &NextCMFastTemplateResource{} +) + +func NewNextCMFastTemplateResource() resource.Resource { + return &NextCMFastTemplateResource{} +} + +type NextCMFastTemplateResource struct { + client *bigipnextsdk.BigipNextCM +} +type NextCMFastTemplateResourceModel struct { + TemplateName types.String `tfsdk:"template_name"` + ApplicationDescription types.String `tfsdk:"application_description"` + ApplicationName types.String `tfsdk:"application_name"` + Pools types.List `tfsdk:"pools"` + Virtuals types.List `tfsdk:"virtuals"` + SetName types.String `tfsdk:"set_name"` + TenantName types.String `tfsdk:"tenant_name"` + AllowOverwrite types.Bool `tfsdk:"allow_overwrite"` + Id types.String `tfsdk:"id"` +} + +type NextCMFastTemplatePoolModel struct { + LoadBalancingMode types.String `tfsdk:"load_balancing_mode"` + MonitorType types.List `tfsdk:"monitor_type"` + PoolName types.String `tfsdk:"pool_name"` + ServicePort types.Int64 `tfsdk:"service_port"` +} + +// pool_name +type NextCMFastTemplateVirtualModel struct { + PoolName types.String `tfsdk:"pool_name"` + VirtualName types.String `tfsdk:"virtual_name"` + VirtualPort types.Int64 `tfsdk:"virtual_port"` +} + +func (r *NextCMFastTemplateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_cm_fast_template" +} + +func (r *NextCMFastTemplateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Resource used to Manages FAST templates on Central Manager", + Attributes: map[string]schema.Attribute{ + "template_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the FAST template to be created", + }, + "template_set": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the FAST template set to use\nIf a set with given name does not exist a new set will be created automatically", + }, + "application_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Application", + }, + "tenant_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Tenant", + }, + "application_description": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Description of the Application", + }, + "pools": schema.ListNestedAttribute{ + Optional: true, + MarkdownDescription: "List of Pools", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "load_balancing_mode": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Load Balancing Mode", + }, + "monitor_type": schema.ListAttribute{ + Required: true, + ElementType: types.StringType, + MarkdownDescription: "Monitor Type", + }, + "pool_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Pool", + }, + "service_port": schema.Int64Attribute{ + Required: true, + MarkdownDescription: "Service Port", + }, + }, + }, + }, + "virtuals": schema.ListNestedAttribute{ + Optional: true, + MarkdownDescription: "List of Virtuals", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "pool_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Pool", + }, + "virtual_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the Virtual", + }, + "virtual_port": schema.Int64Attribute{ + Required: true, + MarkdownDescription: "Virtual Port", + }, + }, + }, + }, + "set_name": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Name of the AS3 Set", + Default: stringdefault.StaticString("Examples"), + }, + "allow_overwrite": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Allow Overwrite", + Default: booldefault.StaticBool(false), + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique Identifier for the resource", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *NextCMFastTemplateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.client, resp.Diagnostics = toBigipNextCMProvider(req.ProviderData) +} + +func (r *NextCMFastTemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var resCfg *NextCMFastTemplateResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + if resp.Diagnostics.HasError() { + return + } + //git tflog.Info(ctx, fmt.Sprintf("[CREATE] NextCMFastTemplateResource:%+v\n", resCfg.Name.ValueString())) + resp.Diagnostics.Append(resp.State.Set(ctx, resCfg)...) +} + +func (r *NextCMFastTemplateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var stateCfg *NextCMFastTemplateResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &stateCfg)...) +} + +func (r *NextCMFastTemplateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var resCfg *NextCMFastTemplateResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &resCfg)...) +} + +func (r *NextCMFastTemplateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var stateCfg *NextCMFastTemplateResourceModel + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) + stateCfg.Id = types.StringValue("") +} + +func (r *NextCMFastTemplateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/fixtures/cm_ha_nodes_get.json b/internal/provider/fixtures/cm_ha_nodes_get.json new file mode 100644 index 0000000..37e6472 --- /dev/null +++ b/internal/provider/fixtures/cm_ha_nodes_get.json @@ -0,0 +1,47 @@ +[ + { + "metadata": { + "id": "979423f5-0e8a-4994-98j92813umio21m3", + "machine_id": "972b14606bf74c8291e95298u3j89ddc", + "name": "central-manager-10.146.165.89" + }, + "spec": { + "node_address": "10.146.165.89", + "node_type": "server" + }, + "status": { + "ready": true, + "registration": "SUCCESSFUL" + } + }, + { + "metadata": { + "id": "979423f5-0e8a-4994-98j92813umio21m3", + "machine_id": "972b14606bf74c8291e95298u3j89ddc", + "name": "central-manager-10.146.164.150" + }, + "spec": { + "node_address": "10.146.164.150", + "node_type": "server" + }, + "status": { + "ready": true, + "registration": "SUCCESSFUL" + } + }, + { + "metadata": { + "id": "979423f5-0e8a-4994-98j92813umio21m3", + "machine_id": "972b14606bf74c8291e95298u3j89ddc", + "name": "central-manager-server" + }, + "spec": { + "node_address": "10.123.111.88", + "node_type": "server" + }, + "status": { + "ready": true, + "registration": "SUCCESSFUL" + } + } +] \ No newline at end of file diff --git a/internal/provider/fixtures/cm_ha_nodes_get_after_delete.json b/internal/provider/fixtures/cm_ha_nodes_get_after_delete.json new file mode 100644 index 0000000..502fef6 --- /dev/null +++ b/internal/provider/fixtures/cm_ha_nodes_get_after_delete.json @@ -0,0 +1,17 @@ +[ + { + "metadata": { + "id": "979423f5-0e8a-4994-98j92813umio21m3", + "machine_id": "972b14606bf74c8291e95298u3j89ddc", + "name": "central-manager-server" + }, + "spec": { + "node_address": "10.123.111.88", + "node_type": "server" + }, + "status": { + "ready": true, + "registration": "SUCCESSFUL" + } + } +] \ No newline at end of file diff --git a/internal/provider/fixtures/cm_ha_nodes_post.json b/internal/provider/fixtures/cm_ha_nodes_post.json new file mode 100644 index 0000000..154ec8e --- /dev/null +++ b/internal/provider/fixtures/cm_ha_nodes_post.json @@ -0,0 +1,32 @@ +[ + { + "metadata": { + "id": "", + "machine_id": "", + "name": "central-manager-10.146.164.150" + }, + "spec": { + "node_address": "10.146.164.150", + "node_type": "server" + }, + "status": { + "ready": false, + "registration": "PENDING" + } + }, + { + "metadata": { + "id": "", + "machine_id": "", + "name": "central-manager-10.146.165.89" + }, + "spec": { + "node_address": "10.146.165.89", + "node_type": "server" + }, + "status": { + "ready": false, + "registration": "PENDING" + } + } +] diff --git a/internal/provider/fixtures/cm_ha_nodes_post_update.json b/internal/provider/fixtures/cm_ha_nodes_post_update.json new file mode 100644 index 0000000..fe7e457 --- /dev/null +++ b/internal/provider/fixtures/cm_ha_nodes_post_update.json @@ -0,0 +1,17 @@ +[ + { + "metadata": { + "id": "", + "machine_id": "", + "name": "central-manager-12.34.56.77" + }, + "spec": { + "node_address": "12.34.56.77", + "node_type": "server" + }, + "status": { + "ready": false, + "registration": "PENDING" + } + } +] diff --git a/internal/provider/fixtures/cm_next_ha_delete_task_status.json b/internal/provider/fixtures/cm_next_ha_delete_task_status.json new file mode 100644 index 0000000..cb6b7ca --- /dev/null +++ b/internal/provider/fixtures/cm_next_ha_delete_task_status.json @@ -0,0 +1,13 @@ +{ + "_links":{ + "self":{ + "href":"/v1/deletion-tasks/642d5964-8cd9-4881-9086-1ed5ca682101" + } + }, + "address":"10.146.168.20", + "created":"2023-11-28T07:55:50.924918Z", + "device_id":"8d6c8c85-1738-4a34-b57b-d3644a2ecfcc", + "id":"642d5964-8cd9-4881-9086-1ed5ca682101", + "state":"factoryResetInstance", + "status":"completed" +} \ No newline at end of file diff --git a/internal/provider/fixtures/cm_next_ha_task_status.json b/internal/provider/fixtures/cm_next_ha_task_status.json new file mode 100644 index 0000000..212fe4b --- /dev/null +++ b/internal/provider/fixtures/cm_next_ha_task_status.json @@ -0,0 +1,41 @@ +{ + "_links":{ + "self":{ + "href":"/v1/ha-creation-tasks/06aea4ed-7425-4db3-a728-2574929885d9" + } + }, + "active_instance_id":"8d6c8c85-1738-4a34-b57b-d3644a2ecfcc", + "auto_failback":false, + "cluster_management_ip":"10.146.168.20", + "cluster_name":"raviecosyshydha", + "control_plane_vlan":{ + "tag":101, + "name":"ha-cp-vlan" + }, + "created":"2023-11-28T05:56:57.618962Z", + "data_plane_vlan":{ + "tag":102, + "name":"ha-dp-vlan", + "NetworkInterface":"1.3" + }, + "id":"06aea4ed-7425-4db3-a728-2574929885d9", + "name":"create HA from 8d6c8c85-1738-4a34-b57b-d3644a2ecfcc", + "nodes":[ + { + "name":"active-node", + "control_plane_address":"10.146.168.21/16", + "data_plane_primary_address":"10.3.0.10/16" + }, + { + "name":"standby-node", + "control_plane_address":"10.146.168.22/16", + "data_plane_primary_address":"10.3.0.10/16" + } + ], + "standby_instance_id":"d0e9cda1-4460-4132-87fd-0f3aa18f3872", + "state":"haGetNodesLoginInfo", + "status":"completed", + "task_type":"instance_ha_creation", + "traffic_vlan":null, + "updated":"2023-11-28T05:56:57.712342Z" +} \ No newline at end of file diff --git a/internal/provider/fixtures/getDeviceIdByHostname.json b/internal/provider/fixtures/getDeviceIdByHostname.json new file mode 100644 index 0000000..8c38fbb --- /dev/null +++ b/internal/provider/fixtures/getDeviceIdByHostname.json @@ -0,0 +1,32 @@ +{ + "_embedded": { + "devices": [ + { + "_links": { + "self": { + "href": "/v1/inventory?filter=hostname+eq+%27big-ip-next%27/494975ee-5eba-420a-90a6-f3aeb75bbf5b" + } + }, + "address": "10.218.132.39", + "certificate_validated": "2024-07-01T07:44:31.174615Z", + "certificate_validation_error": "tls: failed to verify certificate: x509: certificate signed by unknown authority", + "certificate_validity": false, + "hostname": "big-ip-next", + "id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "mode": "STANDALONE", + "platform_name": "KVM", + "platform_type": "VE", + "port": 5443, + "short_id": "fYYPFbrB", + "version": "20.2.1-2.430.2" + } + ] + }, + "_links": { + "self": { + "href": "/v1/inventory?filter=hostname+eq+%27big-ip-next%27" + } + }, + "count": 1, + "total": 1 +} \ No newline at end of file diff --git a/internal/provider/fixtures/getDeviceIdByIp.json b/internal/provider/fixtures/getDeviceIdByIp.json new file mode 100644 index 0000000..01ae756 --- /dev/null +++ b/internal/provider/fixtures/getDeviceIdByIp.json @@ -0,0 +1,32 @@ +{ + "_embedded": { + "devices": [ + { + "_links": { + "self": { + "href": "/v1/inventory?filter=address+eq+%2710.218.132.39%27/494975ee-5eba-420a-90a6-f3aeb75bbf5b" + } + }, + "address": "10.218.132.39", + "certificate_validated": "2024-07-01T07:44:31.174615Z", + "certificate_validation_error": "tls: failed to verify certificate: x509: certificate signed by unknown authority", + "certificate_validity": false, + "hostname": "big-ip-next", + "id": "494975ee-5eba-420a-90a6-f3aeb75bbf5b", + "mode": "STANDALONE", + "platform_name": "KVM", + "platform_type": "VE", + "port": 5443, + "short_id": "fYYPFbrB", + "version": "20.2.1-2.430.2" + } + ] + }, + "_links": { + "self": { + "href": "/v1/inventory?filter=address+eq+%2710.218.132.39%27" + } + }, + "count": 1, + "total": 1 +} \ No newline at end of file diff --git a/internal/provider/fixtures/getWaf.json b/internal/provider/fixtures/getWaf.json new file mode 100644 index 0000000..96a4493 --- /dev/null +++ b/internal/provider/fixtures/getWaf.json @@ -0,0 +1,6583 @@ +{ + "_links": { + "self": { + "href": "/api/v1/spaces/default/security/waf-policies/1a4453fe-b37a-4212-a813-a3d2f789dad1" + } + }, + "application_language": "utf-8", + "created_by": "", + "created_time": "2024-07-17T11:59:25.304423Z", + "declaration": { + "policy": { + "app-protection": { + "enabled": true + }, + "applicationLanguage": "utf-8", + "behavioral-enforcement": { + "behavioralEnforcementViolations": [ + { + "name": "VIOL_GEOLOCATION" + }, + { + "name": "VIOL_URL" + }, + { + "name": "VIOL_FILETYPE" + }, + { + "name": "VIOL_BLACKLISTED_IP" + }, + { + "name": "VIOL_THREAT_CAMPAIGN" + }, + { + "name": "VIOL_BLOCKING_CONDITION" + }, + { + "name": "VIOL_THREAT_ANALYSIS" + }, + { + "name": "VIOL_CONVICTION" + } + ], + "enableBehavioralEnforcement": false, + "enableBlockingCveSignatures": true, + "enableBlockingHighAccuracySignatures": true, + "enableBlockingLikelyMaliciousTransactions": true, + "enableBlockingSuspiciousTransactions": false, + "enableBlockingViolations": true + }, + "blocking-settings": { + "evasions": [ + { + "description": "Trailing slash", + "enabled": false, + "learn": true + }, + { + "description": "Trailing dot", + "enabled": false, + "learn": true + }, + { + "description": "Semicolon path parameters", + "enabled": false, + "learn": true + }, + { + "description": "Bad unescape", + "enabled": true, + "learn": true + }, + { + "description": "Apache whitespace", + "enabled": true, + "learn": true + }, + { + "description": "Bare byte decoding", + "enabled": true, + "learn": true + }, + { + "description": "IIS Unicode codepoints", + "enabled": true, + "learn": true + }, + { + "description": "IIS backslashes", + "enabled": true, + "learn": true + }, + { + "description": "%u decoding", + "enabled": true, + "learn": true + }, + { + "description": "Multiple decoding", + "enabled": true, + "learn": true, + "maxDecodingPasses": 2 + }, + { + "description": "Directory traversals", + "enabled": true, + "learn": true + }, + { + "description": "Multiple slashes", + "enabled": false, + "learn": true + } + ], + "http-protocols": [ + { + "description": "Check maximum number of cookies", + "enabled": false, + "learn": true, + "maxCookies": 50 + }, + { + "description": "Unescaped space in URL", + "enabled": false, + "learn": true + }, + { + "description": "Multiple host headers", + "enabled": true, + "learn": true + }, + { + "description": "Check maximum number of parameters", + "enabled": true, + "learn": true, + "maxParams": 100 + }, + { + "description": "Bad host header value", + "enabled": true, + "learn": true + }, + { + "description": "Check maximum number of headers", + "enabled": true, + "learn": true, + "maxHeaders": 100 + }, + { + "description": "Unparsable request content", + "enabled": true + }, + { + "description": "High ASCII characters in headers", + "enabled": true, + "learn": true + }, + { + "description": "Null in request", + "enabled": true + }, + { + "description": "Bad HTTP version", + "enabled": true + }, + { + "description": "Content length should be a positive number", + "enabled": true, + "learn": true + }, + { + "description": "Host header contains IP address", + "enabled": false, + "learn": true + }, + { + "description": "CRLF characters before request start", + "enabled": true, + "learn": true + }, + { + "description": "No Host header in HTTP/1.1 request", + "enabled": true, + "learn": true + }, + { + "description": "Bad multipart parameters parsing", + "enabled": true, + "learn": true + }, + { + "description": "Bad multipart/form-data request parsing", + "enabled": true, + "learn": false + }, + { + "description": "Body in GET or HEAD requests", + "enabled": false, + "learn": false + }, + { + "description": "Chunked request with Content-Length header", + "enabled": true, + "learn": true + }, + { + "description": "Several Content-Length headers", + "enabled": true, + "learn": true + }, + { + "description": "Header name with no header value", + "enabled": true, + "learn": true + }, + { + "description": "POST request with Content-Length: 0", + "enabled": false, + "learn": true + } + ], + "violations": [ + { + "alarm": true, + "block": true, + "description": "Failed to convert character", + "name": "VIOL_ENCODING" + }, + { + "alarm": true, + "block": false, + "description": "Illegal redirection attempt", + "learn": true, + "name": "VIOL_REDIRECT" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter", + "learn": true, + "name": "VIOL_PARAMETER" + }, + { + "alarm": true, + "block": false, + "description": "Illegal cross-origin request", + "learn": true, + "name": "VIOL_CROSS_ORIGIN_REQUEST" + }, + { + "alarm": true, + "block": true, + "description": "Brute Force: Maximum login attempts are exceeded", + "name": "VIOL_BRUTE_FORCE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal URL length", + "learn": true, + "name": "VIOL_URL_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "Login URL bypassed", + "learn": true, + "name": "VIOL_LOGIN_URL_BYPASSED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter data type", + "learn": true, + "name": "VIOL_PARAMETER_DATA_TYPE" + }, + { + "alarm": false, + "block": false, + "description": "Unauthorized access attempt", + "name": "VIOL_ACCESS_UNAUTHORIZED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal attachment in SOAP message", + "learn": true, + "name": "VIOL_XML_SOAP_ATTACHMENT" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in parameter name", + "learn": true, + "name": "VIOL_PARAMETER_NAME_METACHAR" + }, + { + "alarm": true, + "block": false, + "description": "Host name mismatch", + "name": "VIOL_HOSTNAME_MISMATCH" + }, + { + "alarm": false, + "block": false, + "description": "Missing Access Token", + "name": "VIOL_ACCESS_MISSING" + }, + { + "alarm": false, + "block": false, + "description": "Illegal session ID in URL", + "learn": true, + "name": "VIOL_DYNAMIC_SESSION" + }, + { + "alarm": true, + "block": true, + "description": "GraphQL disallowed pattern in response", + "name": "VIOL_GRAPHQL_ERROR_RESPONSE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal dynamic parameter value", + "learn": true, + "name": "VIOL_PARAMETER_DYNAMIC_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Data Guard: Information leakage detected", + "learn": true, + "name": "VIOL_DATA_GUARD" + }, + { + "alarm": true, + "block": true, + "description": "Malformed JSON data", + "learn": true, + "name": "VIOL_JSON_MALFORMED" + }, + { + "alarm": true, + "block": true, + "description": "Leaked Credentials Detection", + "name": "VIOL_LEAKED_CREDENTIALS" + }, + { + "alarm": true, + "block": false, + "description": "Illegal cookie length", + "learn": true, + "name": "VIOL_COOKIE_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "Illegal query string length", + "learn": true, + "name": "VIOL_QUERY_STRING_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "SOAP method not allowed", + "learn": true, + "name": "VIOL_XML_SOAP_METHOD" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter numeric value", + "learn": true, + "name": "VIOL_PARAMETER_NUMERIC_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal repeated header", + "learn": true, + "name": "VIOL_HEADER_REPEATED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal empty parameter value", + "learn": true, + "name": "VIOL_PARAMETER_EMPTY_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal file type", + "learn": true, + "name": "VIOL_FILETYPE" + }, + { + "alarm": true, + "block": false, + "description": "Access from disallowed User/Session/IP/Device ID", + "name": "VIOL_SESSION_AWARENESS" + }, + { + "alarm": true, + "block": false, + "description": "Login URL expired", + "learn": true, + "name": "VIOL_LOGIN_URL_EXPIRED" + }, + { + "alarm": true, + "block": false, + "description": "XML data does not comply with format settings", + "learn": true, + "name": "VIOL_XML_FORMAT" + }, + { + "alarm": false, + "block": false, + "description": "Mandatory parameter is missing", + "name": "VIOL_MANDATORY_PARAMETER" + }, + { + "alarm": true, + "block": false, + "description": "Illegal request content type", + "learn": true, + "name": "VIOL_URL_CONTENT_TYPE" + }, + { + "alarm": true, + "block": true, + "description": "Bot Client Detected", + "learn": true, + "name": "VIOL_BOT_CLIENT" + }, + { + "alarm": true, + "block": false, + "description": "HTTP protocol compliance failed", + "learn": true, + "name": "VIOL_HTTP_PROTOCOL" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter array value", + "name": "VIOL_PARAMETER_ARRAY_VALUE" + }, + { + "alarm": false, + "block": false, + "description": "GWT data does not comply with format settings", + "learn": true, + "name": "VIOL_GWT_FORMAT" + }, + { + "alarm": true, + "block": true, + "description": "IP is blacklisted", + "name": "VIOL_BLACKLISTED_IP" + }, + { + "alarm": true, + "block": true, + "description": "Malformed XML data", + "learn": true, + "name": "VIOL_XML_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "Null in multi-part parameter value", + "learn": true, + "name": "VIOL_PARAMETER_MULTIPART_NULL_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Server-side access to disallowed host", + "name": "VIOL_SERVER_SIDE_HOST" + }, + { + "alarm": false, + "block": false, + "description": "Expired timestamp", + "learn": true, + "name": "VIOL_COOKIE_EXPIRED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in value", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_METACHAR" + }, + { + "alarm": false, + "block": false, + "description": "Illegal flow to URL", + "learn": true, + "name": "VIOL_FLOW" + }, + { + "alarm": true, + "block": false, + "description": "CSRF attack detected", + "name": "VIOL_CSRF" + }, + { + "alarm": false, + "block": false, + "description": "Illegal entry point", + "learn": true, + "name": "VIOL_FLOW_ENTRY_POINT" + }, + { + "alarm": true, + "block": false, + "description": "Mandatory HTTP header is missing", + "learn": true, + "name": "VIOL_MANDATORY_HEADER" + }, + { + "alarm": true, + "block": false, + "description": "Parameter value does not comply with regular expression", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_REGEXP" + }, + { + "alarm": false, + "block": false, + "description": "Illegal query string or POST data", + "learn": true, + "name": "VIOL_FLOW_DISALLOWED_INPUT" + }, + { + "alarm": true, + "block": false, + "description": "JSON data does not comply with JSON schema", + "name": "VIOL_JSON_SCHEMA" + }, + { + "alarm": true, + "block": false, + "description": "Illegal repeated parameter name", + "learn": true, + "name": "VIOL_PARAMETER_REPEATED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter value length", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "Web Services Security failure", + "learn": true, + "name": "VIOL_XML_WEB_SERVICES_SECURITY" + }, + { + "alarm": true, + "block": false, + "description": "GraphQL data does not comply with format settings", + "learn": true, + "name": "VIOL_GRAPHQL_FORMAT" + }, + { + "alarm": false, + "block": false, + "description": "Virus detected", + "learn": true, + "name": "VIOL_VIRUS" + }, + { + "alarm": true, + "block": false, + "description": "Illegal header length", + "learn": true, + "name": "VIOL_HEADER_LENGTH" + }, + { + "alarm": false, + "block": false, + "description": "Malformed GWT data", + "learn": true, + "name": "VIOL_GWT_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter location", + "name": "VIOL_PARAMETER_LOCATION" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in header", + "learn": true, + "name": "VIOL_HEADER_METACHAR" + }, + { + "alarm": true, + "block": true, + "description": "GraphQL introspection query", + "name": "VIOL_GRAPHQL_INTROSPECTION_QUERY" + }, + { + "alarm": true, + "block": false, + "description": "Evasion technique detected", + "learn": true, + "name": "VIOL_EVASION" + }, + { + "alarm": true, + "block": true, + "description": "Malformed GraphQL data", + "learn": true, + "name": "VIOL_GRAPHQL_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "JSON data does not comply with format settings", + "learn": true, + "name": "VIOL_JSON_FORMAT" + }, + { + "alarm": true, + "block": false, + "description": "Disallowed file upload content detected", + "learn": true, + "name": "VIOL_FILE_UPLOAD" + }, + { + "alarm": true, + "block": true, + "description": "Mitigation action determined by Threat Analysis Platform", + "name": "VIOL_THREAT_ANALYSIS" + }, + { + "alarm": true, + "block": false, + "description": "Illegal static parameter value", + "learn": true, + "name": "VIOL_PARAMETER_STATIC_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "XML data does not comply with schema or WSDL document", + "learn": true, + "name": "VIOL_XML_SCHEMA" + }, + { + "alarm": true, + "block": true, + "description": "Unsupported browser", + "learn": true, + "name": "VIOL_BROWSER" + }, + { + "alarm": true, + "block": false, + "description": "Illegal method", + "learn": true, + "name": "VIOL_METHOD" + }, + { + "alarm": true, + "block": false, + "description": "Illegal Base64 value", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_BASE64" + }, + { + "alarm": true, + "block": true, + "description": "ASM Cookie Hijacking", + "learn": true, + "name": "VIOL_ASM_COOKIE_HIJACKING" + }, + { + "alarm": true, + "block": false, + "description": "DataSafe Data Integrity", + "name": "VIOL_DATA_INTEGRITY" + }, + { + "alarm": true, + "block": false, + "description": "Illegal URL", + "learn": true, + "name": "VIOL_URL" + }, + { + "alarm": true, + "block": true, + "description": "Threat Campaign detected", + "name": "VIOL_THREAT_CAMPAIGN" + }, + { + "description": "Attack signature detected", + "name": "VIOL_ATTACK_SIGNATURE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in URL", + "learn": true, + "name": "VIOL_URL_METACHAR" + }, + { + "alarm": true, + "block": true, + "description": "Access from malicious IP address", + "name": "VIOL_MALICIOUS_IP" + }, + { + "alarm": true, + "block": true, + "description": "Request length exceeds defined buffer size", + "learn": true, + "name": "VIOL_REQUEST_MAX_LENGTH" + }, + { + "alarm": false, + "block": false, + "description": "Bad Actor Detected", + "name": "VIOL_MALICIOUS_DEVICE" + }, + { + "alarm": true, + "block": true, + "description": "Modified WAF cookie", + "learn": true, + "name": "VIOL_ASM_COOKIE_MODIFIED" + }, + { + "alarm": true, + "block": false, + "description": "Violation Rating Need Examination detected", + "name": "VIOL_RATING_NEED_EXAMINATION" + }, + { + "alarm": true, + "block": false, + "description": "CSRF authentication expired", + "learn": true, + "name": "VIOL_CSRF_EXPIRED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal request length", + "learn": true, + "name": "VIOL_REQUEST_LENGTH" + }, + { + "alarm": true, + "block": true, + "description": "Blocking Condition Detected", + "name": "VIOL_BLOCKING_CONDITION" + }, + { + "alarm": true, + "block": false, + "description": "Modified domain cookie(s)", + "learn": true, + "name": "VIOL_COOKIE_MODIFIED" + }, + { + "alarm": true, + "block": false, + "description": "Mandatory request body is missing", + "name": "VIOL_MANDATORY_REQUEST_BODY" + }, + { + "alarm": false, + "block": false, + "description": "Access token does not comply with the profile requirements", + "name": "VIOL_ACCESS_INVALID" + }, + { + "alarm": false, + "block": false, + "description": "Illegal host name", + "name": "VIOL_HOSTNAME" + }, + { + "alarm": true, + "block": true, + "description": "Cookie not RFC-compliant", + "learn": true, + "name": "VIOL_COOKIE_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "Disallowed file upload content detected in body", + "name": "VIOL_FILE_UPLOAD_IN_BODY" + }, + { + "alarm": true, + "block": false, + "description": "Illegal POST data length", + "learn": true, + "name": "VIOL_POST_DATA_LENGTH" + }, + { + "alarm": true, + "block": true, + "description": "Access from disallowed Geolocation", + "learn": true, + "name": "VIOL_GEOLOCATION" + }, + { + "alarm": false, + "block": false, + "description": "Illegal number of mandatory parameters", + "learn": true, + "name": "VIOL_FLOW_MANDATORY_PARAMS" + }, + { + "alarm": true, + "block": true, + "description": "Violation Rating Threat detected", + "name": "VIOL_RATING_THREAT" + }, + { + "alarm": true, + "block": true, + "description": "Illegal HTTP status in response", + "learn": true, + "name": "VIOL_HTTP_RESPONSE_STATUS" + }, + { + "alarm": false, + "block": false, + "description": "Malformed Access Token", + "name": "VIOL_ACCESS_MALFORMED" + } + ], + "web-services-securities": [ + { + "description": "UnSigned Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Timestamp expiration is too far in the future", + "enabled": false, + "learn": true + }, + { + "description": "Expired Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Invalid Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Missing Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Verification Error", + "enabled": false, + "learn": true + }, + { + "description": "Signing Error", + "enabled": false, + "learn": true + }, + { + "description": "Encryption Error", + "enabled": false, + "learn": true + }, + { + "description": "Decryption Error", + "enabled": false, + "learn": true + }, + { + "description": "Certificate Error", + "enabled": false, + "learn": true + }, + { + "description": "Certificate Expired", + "enabled": false, + "learn": true + }, + { + "description": "Malformed Error", + "enabled": false, + "learn": true + }, + { + "description": "Internal Error", + "enabled": false, + "learn": true + } + ] + }, + "bot-defense": { + "mitigations": { + "classes": [ + { + "action": "block", + "name": "malicious-bot" + }, + { + "action": "ignore", + "name": "suspicious-browser" + }, + { + "action": "ignore", + "name": "untrusted-bot" + }, + { + "action": "ignore", + "name": "trusted-bot" + }, + { + "action": "detect", + "name": "browser" + }, + { + "action": "ignore", + "name": "unknown" + } + ] + }, + "settings": { + "caseSensitiveHttpHeaders": true, + "isEnabled": true + } + }, + "brute-force-attack-preventions": [ + { + "bruteForceProtectionForAllLoginPages": false, + "captchaBypassCriteria": { + "action": "alarm-and-drop", + "enabled": false, + "threshold": 5 + }, + "clientSideIntegrityBypassCriteria": { + "action": "alarm-and-captcha", + "threshold": 3 + }, + "detectionCriteria": { + "action": "alarm", + "credentialsStuffingMatchesReached": 100, + "failedLoginAttemptsRateReached": 100 + }, + "leakedCredentialsCriteria": { + "action": "alarm-and-blocking-page" + }, + "loginAttemptsFromTheSameDeviceId": { + "action": "alarm", + "threshold": 3 + }, + "loginAttemptsFromTheSameIp": { + "action": "alarm-and-blocking-page", + "enabled": true, + "threshold": 20 + }, + "loginAttemptsFromTheSameUser": { + "action": "alarm", + "enabled": true, + "threshold": 3 + }, + "measurementPeriod": 900, + "preventionDuration": "3600", + "reEnableLoginAfter": 3600, + "sourceBasedProtectionDetectionPeriod": 3600 + } + ], + "caseInsensitive": true, + "character-sets": [ + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": true, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": true, + "metachar": "0x24" + }, + { + "isAllowed": true, + "metachar": "0x25" + }, + { + "isAllowed": true, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": true, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": true, + "metachar": "0x3a" + }, + { + "isAllowed": true, + "metachar": "0x3b" + }, + { + "isAllowed": true, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": true, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": true, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": true, + "metachar": "0x5b" + }, + { + "isAllowed": true, + "metachar": "0x5c" + }, + { + "isAllowed": true, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": true, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": true, + "metachar": "0x7d" + }, + { + "isAllowed": true, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + }, + { + "isAllowed": false, + "metachar": "0x80" + }, + { + "isAllowed": false, + "metachar": "0x81" + }, + { + "isAllowed": false, + "metachar": "0x82" + }, + { + "isAllowed": false, + "metachar": "0x83" + }, + { + "isAllowed": false, + "metachar": "0x84" + }, + { + "isAllowed": false, + "metachar": "0x85" + }, + { + "isAllowed": false, + "metachar": "0x86" + }, + { + "isAllowed": false, + "metachar": "0x87" + }, + { + "isAllowed": false, + "metachar": "0x88" + }, + { + "isAllowed": false, + "metachar": "0x89" + }, + { + "isAllowed": false, + "metachar": "0x8a" + }, + { + "isAllowed": false, + "metachar": "0x8b" + }, + { + "isAllowed": false, + "metachar": "0x8c" + }, + { + "isAllowed": false, + "metachar": "0x8d" + }, + { + "isAllowed": false, + "metachar": "0x8e" + }, + { + "isAllowed": false, + "metachar": "0x8f" + }, + { + "isAllowed": false, + "metachar": "0x90" + }, + { + "isAllowed": false, + "metachar": "0x91" + }, + { + "isAllowed": false, + "metachar": "0x92" + }, + { + "isAllowed": false, + "metachar": "0x93" + }, + { + "isAllowed": false, + "metachar": "0x94" + }, + { + "isAllowed": false, + "metachar": "0x95" + }, + { + "isAllowed": false, + "metachar": "0x96" + }, + { + "isAllowed": false, + "metachar": "0x97" + }, + { + "isAllowed": false, + "metachar": "0x98" + }, + { + "isAllowed": false, + "metachar": "0x99" + }, + { + "isAllowed": false, + "metachar": "0x9a" + }, + { + "isAllowed": false, + "metachar": "0x9b" + }, + { + "isAllowed": false, + "metachar": "0x9c" + }, + { + "isAllowed": false, + "metachar": "0x9d" + }, + { + "isAllowed": false, + "metachar": "0x9e" + }, + { + "isAllowed": false, + "metachar": "0x9f" + }, + { + "isAllowed": false, + "metachar": "0xa0" + }, + { + "isAllowed": false, + "metachar": "0xa1" + }, + { + "isAllowed": false, + "metachar": "0xa2" + }, + { + "isAllowed": false, + "metachar": "0xa3" + }, + { + "isAllowed": false, + "metachar": "0xa4" + }, + { + "isAllowed": false, + "metachar": "0xa5" + }, + { + "isAllowed": false, + "metachar": "0xa6" + }, + { + "isAllowed": false, + "metachar": "0xa7" + }, + { + "isAllowed": false, + "metachar": "0xa8" + }, + { + "isAllowed": false, + "metachar": "0xa9" + }, + { + "isAllowed": false, + "metachar": "0xaa" + }, + { + "isAllowed": false, + "metachar": "0xab" + }, + { + "isAllowed": false, + "metachar": "0xac" + }, + { + "isAllowed": false, + "metachar": "0xad" + }, + { + "isAllowed": false, + "metachar": "0xae" + }, + { + "isAllowed": false, + "metachar": "0xaf" + }, + { + "isAllowed": false, + "metachar": "0xb0" + }, + { + "isAllowed": false, + "metachar": "0xb1" + }, + { + "isAllowed": false, + "metachar": "0xb2" + }, + { + "isAllowed": false, + "metachar": "0xb3" + }, + { + "isAllowed": false, + "metachar": "0xb4" + }, + { + "isAllowed": false, + "metachar": "0xb5" + }, + { + "isAllowed": false, + "metachar": "0xb6" + }, + { + "isAllowed": false, + "metachar": "0xb7" + }, + { + "isAllowed": false, + "metachar": "0xb8" + }, + { + "isAllowed": false, + "metachar": "0xb9" + }, + { + "isAllowed": false, + "metachar": "0xba" + }, + { + "isAllowed": false, + "metachar": "0xbb" + }, + { + "isAllowed": false, + "metachar": "0xbc" + }, + { + "isAllowed": false, + "metachar": "0xbd" + }, + { + "isAllowed": false, + "metachar": "0xbe" + }, + { + "isAllowed": false, + "metachar": "0xbf" + }, + { + "isAllowed": true, + "metachar": "0xc0" + }, + { + "isAllowed": true, + "metachar": "0xc1" + }, + { + "isAllowed": true, + "metachar": "0xc2" + }, + { + "isAllowed": true, + "metachar": "0xc3" + }, + { + "isAllowed": true, + "metachar": "0xc4" + }, + { + "isAllowed": true, + "metachar": "0xc5" + }, + { + "isAllowed": true, + "metachar": "0xc6" + }, + { + "isAllowed": true, + "metachar": "0xc7" + }, + { + "isAllowed": true, + "metachar": "0xc8" + }, + { + "isAllowed": true, + "metachar": "0xc9" + }, + { + "isAllowed": true, + "metachar": "0xca" + }, + { + "isAllowed": true, + "metachar": "0xcb" + }, + { + "isAllowed": true, + "metachar": "0xcc" + }, + { + "isAllowed": true, + "metachar": "0xcd" + }, + { + "isAllowed": true, + "metachar": "0xce" + }, + { + "isAllowed": true, + "metachar": "0xcf" + }, + { + "isAllowed": true, + "metachar": "0xd0" + }, + { + "isAllowed": true, + "metachar": "0xd1" + }, + { + "isAllowed": true, + "metachar": "0xd2" + }, + { + "isAllowed": true, + "metachar": "0xd3" + }, + { + "isAllowed": true, + "metachar": "0xd4" + }, + { + "isAllowed": true, + "metachar": "0xd5" + }, + { + "isAllowed": true, + "metachar": "0xd6" + }, + { + "isAllowed": false, + "metachar": "0xd7" + }, + { + "isAllowed": true, + "metachar": "0xd8" + }, + { + "isAllowed": true, + "metachar": "0xd9" + }, + { + "isAllowed": true, + "metachar": "0xda" + }, + { + "isAllowed": true, + "metachar": "0xdb" + }, + { + "isAllowed": true, + "metachar": "0xdc" + }, + { + "isAllowed": true, + "metachar": "0xdd" + }, + { + "isAllowed": true, + "metachar": "0xde" + }, + { + "isAllowed": true, + "metachar": "0xdf" + }, + { + "isAllowed": true, + "metachar": "0xe0" + }, + { + "isAllowed": true, + "metachar": "0xe1" + }, + { + "isAllowed": true, + "metachar": "0xe2" + }, + { + "isAllowed": true, + "metachar": "0xe3" + }, + { + "isAllowed": true, + "metachar": "0xe4" + }, + { + "isAllowed": true, + "metachar": "0xe5" + }, + { + "isAllowed": true, + "metachar": "0xe6" + }, + { + "isAllowed": true, + "metachar": "0xe7" + }, + { + "isAllowed": true, + "metachar": "0xe8" + }, + { + "isAllowed": true, + "metachar": "0xe9" + }, + { + "isAllowed": true, + "metachar": "0xea" + }, + { + "isAllowed": true, + "metachar": "0xeb" + }, + { + "isAllowed": true, + "metachar": "0xec" + }, + { + "isAllowed": true, + "metachar": "0xed" + }, + { + "isAllowed": true, + "metachar": "0xee" + }, + { + "isAllowed": true, + "metachar": "0xef" + }, + { + "isAllowed": true, + "metachar": "0xf0" + }, + { + "isAllowed": true, + "metachar": "0xf1" + }, + { + "isAllowed": true, + "metachar": "0xf2" + }, + { + "isAllowed": true, + "metachar": "0xf3" + }, + { + "isAllowed": true, + "metachar": "0xf4" + }, + { + "isAllowed": true, + "metachar": "0xf5" + }, + { + "isAllowed": true, + "metachar": "0xf6" + }, + { + "isAllowed": false, + "metachar": "0xf7" + }, + { + "isAllowed": true, + "metachar": "0xf8" + }, + { + "isAllowed": true, + "metachar": "0xf9" + }, + { + "isAllowed": true, + "metachar": "0xfa" + }, + { + "isAllowed": true, + "metachar": "0xfb" + }, + { + "isAllowed": true, + "metachar": "0xfc" + }, + { + "isAllowed": true, + "metachar": "0xfd" + }, + { + "isAllowed": true, + "metachar": "0xfe" + }, + { + "isAllowed": true, + "metachar": "0xff" + } + ], + "characterSetType": "header" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": true, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": false, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "url" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": false, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "parameter-name" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": false, + "metachar": "0x28" + }, + { + "isAllowed": false, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": false, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "parameter-value" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": true, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": true, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": false, + "metachar": "0x28" + }, + { + "isAllowed": false, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": false, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "xml-content" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": false, + "metachar": "0x28" + }, + { + "isAllowed": false, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": false, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "json-content" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": true, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": true, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": true, + "metachar": "0x26" + }, + { + "isAllowed": true, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": true, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": true, + "metachar": "0x3a" + }, + { + "isAllowed": true, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": true, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": true, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": true, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": true, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": true, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "gwt-content" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": true, + "metachar": "0x21" + }, + { + "isAllowed": true, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": true, + "metachar": "0x24" + }, + { + "isAllowed": true, + "metachar": "0x25" + }, + { + "isAllowed": true, + "metachar": "0x26" + }, + { + "isAllowed": true, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": true, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": true, + "metachar": "0x3a" + }, + { + "isAllowed": true, + "metachar": "0x3b" + }, + { + "isAllowed": true, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": true, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": true, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": true, + "metachar": "0x5b" + }, + { + "isAllowed": true, + "metachar": "0x5c" + }, + { + "isAllowed": true, + "metachar": "0x5d" + }, + { + "isAllowed": true, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": true, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": true, + "metachar": "0x7b" + }, + { + "isAllowed": true, + "metachar": "0x7c" + }, + { + "isAllowed": true, + "metachar": "0x7d" + }, + { + "isAllowed": true, + "metachar": "0x7e" + }, + { + "isAllowed": true, + "metachar": "0x7f" + } + ], + "characterSetType": "plain-text-content" + } + ], + "cookie-settings": { + "maximumCookieHeaderLength": "4096" + }, + "cookies": [ + { + "accessibleOnlyThroughTheHttpProtocol": false, + "attackSignaturesCheck": true, + "enforcementType": "allow", + "insertSameSiteAttribute": "lax", + "isBase64": false, + "maskValueInLogs": false, + "name": "*", + "performStaging": false, + "securedOverHttpsConnection": true, + "type": "wildcard", + "wildcardOrder": 1 + } + ], + "csrf-protection": { + "enabled": false + }, + "csrf-urls": [ + { + "enforcementAction": "verify-origin", + "method": "POST", + "requiredParameters": "ignore", + "url": "*", + "wildcardOrder": 1 + } + ], + "data-guard": { + "enabled": false, + "enforcementMode": "ignore-urls-in-list" + }, + "description": "new_waf_policy desc", + "dos-protection": { + "behavioral-dos": { + "badActorDetection": { + "enableTlsIndexing": true, + "enabled": true + }, + "enableHttpSignatures": true, + "enableTlsSignatures": false, + "mitigationLevel": "standard" + }, + "enabled": false + }, + "enablePassiveMode": false, + "enforcementMode": "blocking", + "filetypes": [ + { + "allowed": false, + "name": "bat" + }, + { + "allowed": false, + "name": "sav" + }, + { + "allowed": false, + "name": "pfx" + }, + { + "allowed": false, + "name": "eml" + }, + { + "allowed": false, + "name": "wmz" + }, + { + "allowed": false, + "name": "crt" + }, + { + "allowed": false, + "name": "nws" + }, + { + "allowed": false, + "name": "idq" + }, + { + "allowed": false, + "name": "conf" + }, + { + "allowed": false, + "name": "dat" + }, + { + "allowed": false, + "name": "msi" + }, + { + "allowed": false, + "name": "key" + }, + { + "allowed": false, + "name": "temp" + }, + { + "allowed": false, + "name": "idc" + }, + { + "allowed": false, + "name": "pol" + }, + { + "allowed": false, + "name": "cer" + }, + { + "allowed": false, + "name": "pem" + }, + { + "allowed": false, + "name": "ida" + }, + { + "allowed": false, + "name": "stm" + }, + { + "allowed": false, + "name": "log" + }, + { + "allowed": false, + "name": "sys" + }, + { + "allowed": false, + "name": "p7c" + }, + { + "allowed": false, + "name": "cgi" + }, + { + "allowed": false, + "name": "ini" + }, + { + "allowed": false, + "name": "shtm" + }, + { + "allowed": false, + "name": "cfg" + }, + { + "allowed": false, + "name": "dll" + }, + { + "allowed": false, + "name": "save" + }, + { + "allowed": false, + "name": "htr" + }, + { + "allowed": false, + "name": "hta" + }, + { + "allowed": false, + "name": "bck" + }, + { + "allowed": false, + "name": "exe" + }, + { + "allowed": false, + "name": "old" + }, + { + "allowed": false, + "name": "p7b" + }, + { + "allowed": false, + "name": "com" + }, + { + "allowed": false, + "name": "cmd" + }, + { + "allowed": false, + "name": "tmp" + }, + { + "allowed": false, + "name": "reg" + }, + { + "allowed": false, + "name": "der" + }, + { + "allowed": false, + "name": "htw" + }, + { + "allowed": false, + "name": "shtml" + }, + { + "allowed": false, + "name": "bkp" + }, + { + "allowed": false, + "name": "p12" + }, + { + "allowed": false, + "name": "bak" + }, + { + "allowed": false, + "name": "config" + }, + { + "allowed": false, + "name": "printer" + }, + { + "allowed": false, + "name": "bin" + }, + { + "allowed": true, + "checkPostDataLength": true, + "checkQueryStringLength": true, + "checkRequestLength": true, + "checkUrlLength": true, + "name": "*", + "performStaging": false, + "postDataLength": 4096, + "queryStringLength": 2048, + "requestLength": 8196, + "responseCheck": false, + "responseCheckLength": 20000, + "type": "wildcard", + "urlLength": 1024, + "wildcardOrder": 1 + } + ], + "fullPath": "/Common/Rating-Based-Template", + "general": { + "allowedResponseCodes": [ + 400, + 401, + 403, + 404, + 405, + 406, + 407, + 415, + 417, + 503 + ], + "enableEventCorrelation": true, + "enforcementReadinessPeriod": 7, + "maskCreditCardNumbersInRequest": true, + "pathParameterHandling": "as-parameters", + "triggerAsmIruleEvent": "disabled", + "trustXff": false, + "useDynamicSessionIdInUrl": false + }, + "graphql-profiles": [ + { + "attackSignaturesCheck": true, + "defenseAttributes": { + "allowIntrospectionQueries": false, + "maximumBatchedQueries": "any", + "maximumStructureDepth": 10, + "maximumTotalLength": "any", + "maximumValueLength": "any", + "tolerateParsingWarnings": false + }, + "description": "Default GraphQL Profile", + "hasIdlFiles": false, + "metacharElementCheck": true, + "name": "Default", + "responseEnforcement": { + "blockDisallowedPatterns": false + } + } + ], + "gwt-profiles": [ + { + "attackSignaturesCheck": true, + "defenseAttributes": { + "maximumTotalLengthOfGWTData": 10000, + "maximumValueLength": 100, + "tolerateGWTParsingWarnings": false + }, + "description": "Default GWT Profile", + "metacharElementCheck": true, + "name": "Default" + } + ], + "header-settings": { + "maximumHttpHeaderLength": "8192" + }, + "headers": [ + { + "allowRepeatedOccurrences": false, + "checkSignatures": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "cookie", + "type": "explicit" + }, + { + "allowRepeatedOccurrences": true, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "referer", + "normalizationViolations": true, + "percentDecoding": false, + "type": "explicit", + "urlNormalization": true + }, + { + "allowRepeatedOccurrences": false, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "transfer-encoding", + "normalizationViolations": false, + "percentDecoding": false, + "type": "explicit", + "urlNormalization": false + }, + { + "allowRepeatedOccurrences": false, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": true, + "name": "authorization", + "normalizationViolations": false, + "percentDecoding": true, + "type": "explicit", + "urlNormalization": false + }, + { + "allowRepeatedOccurrences": true, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "*", + "normalizationViolations": false, + "percentDecoding": true, + "type": "wildcard", + "urlNormalization": false, + "wildcardOrder": 1 + } + ], + "ip-intelligence": { + "enabled": true, + "ipIntelligenceCategories": [ + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Spam Sources" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Windows Exploits" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Web Attacks" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "BotNets" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Scanners" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Denial of Service" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Infected Sources" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Phishing Proxies" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Anonymous Proxy" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Cloud-based Services" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Mobile Threats" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Tor Proxies" + } + ] + }, + "json-profiles": [ + { + "defenseAttributes": { + "maximumArrayLength": 1000, + "maximumStructureDepth": 10, + "maximumTotalLengthOfJSONData": 10000, + "maximumValueLength": 100, + "tolerateJSONParsingWarnings": false + }, + "description": "Default JSON Profile", + "handleJsonValuesAsParameters": true, + "hasValidationFiles": false, + "name": "Default", + "validationFiles": [] + } + ], + "login-enforcement": { + "expirationTimePeriod": "disabled" + }, + "methods": [ + { + "actAsMethod": "POST", + "name": "PATCH" + }, + { + "actAsMethod": "GET", + "name": "DELETE" + }, + { + "actAsMethod": "GET", + "name": "OPTIONS" + }, + { + "actAsMethod": "GET", + "name": "HEAD" + }, + { + "actAsMethod": "POST", + "name": "PUT" + }, + { + "actAsMethod": "GET", + "name": "GET" + }, + { + "actAsMethod": "POST", + "name": "POST" + } + ], + "name": "new_waf_policy", + "parameters": [ + { + "allowEmptyValue": true, + "allowRepeatedParameterName": false, + "attackSignaturesCheck": true, + "checkMaxValueLength": false, + "checkMetachars": true, + "isBase64": false, + "isCookie": false, + "isHeader": false, + "level": "global", + "metacharsOnParameterValueCheck": true, + "name": "*", + "parameterLocation": "any", + "performStaging": false, + "sensitiveParameter": false, + "type": "wildcard", + "valueType": "auto-detect", + "wildcardOrder": 1 + } + ], + "performStaging": true, + "policy-builder": { + "enableFullPolicyInspection": true, + "enableTrustedTrafficSiteChangeTracking": true, + "enableUntrustedTrafficSiteChangeTracking": true, + "inactiveEntityInactivityDurationInDays": 90, + "learnFromResponses": false, + "learnInactiveEntities": false, + "learnOnlyFromNonBotTraffic": true, + "learningMode": "on-demand", + "responseStatusCodes": [ + "2xx", + "3xx", + "1xx" + ], + "trafficTighten": { + "maxModificationSuggestionScore": 50, + "minDaysBetweenSamples": 1, + "totalRequests": 15000 + }, + "trustAllIps": false, + "trustedTrafficLoosen": { + "differentSources": 1, + "maxDaysBetweenSamples": 7, + "minHoursBetweenSamples": 0 + }, + "trustedTrafficSiteChangeTracking": { + "differentSources": 1, + "maxDaysBetweenSamples": 7, + "minMinutesBetweenSamples": 0 + }, + "untrustedTrafficLoosen": { + "differentSources": 20, + "maxDaysBetweenSamples": 7, + "minHoursBetweenSamples": 1 + }, + "untrustedTrafficSiteChangeTracking": { + "differentSources": 10, + "maxDaysBetweenSamples": 7, + "minMinutesBetweenSamples": 20 + } + }, + "policy-builder-central-configuration": { + "buildingMode": "local", + "eventCorrelationMode": "local" + }, + "policy-builder-cookie": { + "collapseCookiesIntoOneEntity": false, + "enforceUnmodifiedCookies": false, + "learnExplicitCookies": "never", + "maximumCookies": 100 + }, + "policy-builder-filetype": { + "learnExplicitFiletypes": "never", + "maximumFileTypes": 100 + }, + "policy-builder-header": { + "maximumHosts": 10000, + "validHostNames": false + }, + "policy-builder-parameter": { + "classifyParameters": false, + "collapseParametersIntoOneEntity": false, + "dynamicParameters": { + "allHiddenFields": false, + "formParameters": false, + "linkParameters": false, + "uniqueValueSets": 10 + }, + "learnExplicitParameters": "never", + "maximumParameters": 10000, + "parameterLearningLevel": "global", + "parametersIntegerValue": false + }, + "policy-builder-redirection-protection": { + "learnExplicitRedirectionDomains": "never", + "maximumRedirectionDomains": 100 + }, + "policy-builder-server-technologies": { + "enableServerTechnologiesDetection": false + }, + "policy-builder-sessions-and-logins": { + "learnLoginPage": false + }, + "policy-builder-url": { + "classifyUrls": false, + "classifyWebsocketUrls": false, + "collapseUrlsIntoOneEntity": false, + "learnExplicitUrls": "never", + "learnExplicitWebsocketUrls": "never", + "learnMethodsOnUrls": false, + "maximumUrls": 100, + "maximumWebsocketUrls": 100, + "wildcardUrlFiletypes": [ + "swf", + "wav", + "bmp", + "gif", + "pcx", + "pdf", + "png", + "jpeg", + "ico", + "jpg" + ] + }, + "protocolIndependent": true, + "redirection-protection": { + "redirectionProtectionEnabled": false + }, + "response-pages": [ + { + "responseActionType": "soap-fault", + "responsePageType": "xml" + }, + { + "responseActionType": "default", + "responsePageType": "graphql" + }, + { + "responseActionType": "default", + "responsePageType": "default" + }, + { + "responseActionType": "default", + "responsePageType": "captcha" + }, + { + "ajaxActionType": "alert-popup", + "ajaxPopupMessage": "Login Failed. Username or password is incorrect. Please try to log in again.", + "responsePageType": "failed-login-honeypot-ajax" + }, + { + "ajaxActionType": "alert-popup", + "ajaxPopupMessage": "The requested URL was rejected. Please consult with your administrator. Your support ID is: <%TS.request.ID()%>", + "responsePageType": "ajax-login" + }, + { + "responseActionType": "default", + "responsePageType": "hijack" + }, + { + "responseActionType": "default", + "responsePageType": "captcha-fail" + }, + { + "grpcStatusCode": "UNKNOWN", + "grpcStatusMessage": "The request was rejected. Please consult with your administrator. Your support ID is: <%TS.request.ID()%>", + "responsePageType": "grpc" + }, + { + "responseActionType": "default", + "responsePageType": "persistent-flow" + }, + { + "ajaxActionType": "alert-popup", + "ajaxEnabled": false, + "ajaxPopupMessage": "The requested URL was rejected. Please consult with your administrator. Your support ID is: <%TS.request.ID()%>", + "responsePageType": "ajax" + }, + { + "responseActionType": "default", + "responsePageType": "failed-login-honeypot" + }, + { + "responseActionType": "default", + "responsePageType": "mobile" + } + ], + "sensitive-parameters": [ + { + "name": "password" + } + ], + "signature-sets": [ + { + "name": "High Accuracy Signatures", + "alarm": true, + "block": false, + "learn": true + }, + { + "name": "All Signatures", + "alarm": true, + "block": false, + "learn": true + } + ], + "signature-settings": { + "attackSignatureFalsePositiveMode": "disabled", + "minimumAccuracyForAutoAddedSignatures": "high", + "placeSignaturesInStaging": false, + "signatureStaging": false, + "stagingCertificationDatetime": "" + }, + "softwareVersion": "17.0.0", + "ssrf-hosts": [ + { + "action": "disallow", + "host": "*.burpcollaborator.net" + }, + { + "action": "disallow", + "host": "metadata.google.internal" + }, + { + "action": "disallow", + "host": "localhost" + }, + { + "action": "disallow", + "host": "rancher-metadata" + }, + { + "action": "disallow", + "host": "127.0.0.0/24" + }, + { + "action": "disallow", + "host": "192.0.2.0/24" + }, + { + "action": "disallow", + "host": "198.51.100.0/24" + }, + { + "action": "disallow", + "host": "203.0.113.0/24" + }, + { + "action": "disallow", + "host": "169.254.0.0/16" + }, + { + "action": "disallow", + "host": "192.168.0.0/16" + }, + { + "action": "disallow", + "host": "172.16.0.0/12" + }, + { + "action": "disallow", + "host": "fe80::/10" + }, + { + "action": "disallow", + "host": "10.0.0.0/8" + }, + { + "action": "disallow", + "host": "127.0.0.0/8" + }, + { + "action": "disallow", + "host": "0.0.0.0" + }, + { + "action": "disallow", + "host": "0:0:0:0:0:ffff:7f00:1" + }, + { + "action": "disallow", + "host": "100.100.100.200" + }, + { + "action": "disallow", + "host": "168.63.129.16" + }, + { + "action": "disallow", + "host": "169.254.169.254" + }, + { + "action": "disallow", + "host": "192.0.0.192" + }, + { + "action": "disallow", + "host": "::" + }, + { + "action": "disallow", + "host": "::1" + }, + { + "action": "resolve", + "host": "*" + } + ], + "template": { + "name": "" + }, + "threat-campaign-settings": { + "threatCampaignEnforcementReadinessPeriod": 1, + "threatCampaignStaging": false + }, + "type": "security", + "urls": [ + { + "attackSignaturesCheck": true, + "clickjackingProtection": false, + "description": "", + "disallowFileUploadOfExecutables": true, + "html5CrossOriginRequestsEnforcement": { + "enforcementMode": "enforce" + }, + "isAllowed": true, + "mandatoryBody": false, + "metacharsOnUrlCheck": true, + "method": "*", + "methodsOverrideOnUrlCheck": false, + "name": "*", + "performStaging": false, + "protocol": "http", + "type": "wildcard", + "urlContentProfiles": [ + { + "headerName": "*", + "headerOrder": "default", + "headerValue": "*", + "type": "apply-value-and-content-signatures" + }, + { + "headerName": "Content-Type", + "headerOrder": "1", + "headerValue": "*form*", + "type": "form-data" + }, + { + "contentProfile": { + "name": "Default" + }, + "headerName": "Content-Type", + "headerOrder": "2", + "headerValue": "*json*", + "type": "json" + }, + { + "contentProfile": { + "name": "Default" + }, + "headerName": "Content-Type", + "headerOrder": "3", + "headerValue": "*xml*", + "type": "xml" + } + ], + "wildcardIncludesSlash": true, + "wildcardOrder": 2 + } + ], + "xml-profiles": [ + { + "attachmentsInSoapMessages": false, + "attackSignaturesCheck": true, + "defenseAttributes": { + "allowCDATA": false, + "allowDTDs": false, + "allowExternalReferences": false, + "allowProcessingInstructions": true, + "maximumAttributeValueLength": 1024, + "maximumAttributesPerElement": 16, + "maximumChildrenPerElement": 1024, + "maximumDocumentDepth": 32, + "maximumDocumentSize": 1024000, + "maximumElements": 65536, + "maximumNSDeclarations": 64, + "maximumNameLength": 256, + "maximumNamespaceLength": 256, + "tolerateCloseTagShorthand": false, + "tolerateLeadingWhiteSpace": false, + "tolerateNumericNames": false + }, + "description": "Default XML Profile", + "enableWss": false, + "followSchemaLinks": false, + "inspectSoapAttachments": false, + "metacharAttributeCheck": false, + "metacharElementCheck": true, + "name": "Default", + "useXmlResponsePage": false, + "validationFiles": [], + "validationSoapActionHeader": false + } + ] + } + }, + "enforcement_mode": "blocking", + "id": "1a4453fe-b37a-4212-a813-a3d2f789dad1", + "last_modified": "2024-07-17T11:59:25.304421Z", + "name": "new_waf_policy" +} \ No newline at end of file diff --git a/internal/provider/fixtures/getWafUpdated.json b/internal/provider/fixtures/getWafUpdated.json new file mode 100644 index 0000000..4ccc049 --- /dev/null +++ b/internal/provider/fixtures/getWafUpdated.json @@ -0,0 +1,6583 @@ +{ + "_links": { + "self": { + "href": "/api/v1/spaces/default/security/waf-policies/1a4453fe-b37a-4212-a813-a3d2f789dad1" + } + }, + "application_language": "utf-8", + "created_by": "", + "created_time": "2024-07-17T11:59:25.304423Z", + "declaration": { + "policy": { + "app-protection": { + "enabled": true + }, + "applicationLanguage": "utf-8", + "behavioral-enforcement": { + "behavioralEnforcementViolations": [ + { + "name": "VIOL_GEOLOCATION" + }, + { + "name": "VIOL_URL" + }, + { + "name": "VIOL_FILETYPE" + }, + { + "name": "VIOL_BLACKLISTED_IP" + }, + { + "name": "VIOL_THREAT_CAMPAIGN" + }, + { + "name": "VIOL_BLOCKING_CONDITION" + }, + { + "name": "VIOL_THREAT_ANALYSIS" + }, + { + "name": "VIOL_CONVICTION" + } + ], + "enableBehavioralEnforcement": false, + "enableBlockingCveSignatures": true, + "enableBlockingHighAccuracySignatures": true, + "enableBlockingLikelyMaliciousTransactions": true, + "enableBlockingSuspiciousTransactions": false, + "enableBlockingViolations": true + }, + "blocking-settings": { + "evasions": [ + { + "description": "Trailing slash", + "enabled": false, + "learn": true + }, + { + "description": "Trailing dot", + "enabled": false, + "learn": true + }, + { + "description": "Semicolon path parameters", + "enabled": false, + "learn": true + }, + { + "description": "Bad unescape", + "enabled": true, + "learn": true + }, + { + "description": "Apache whitespace", + "enabled": true, + "learn": true + }, + { + "description": "Bare byte decoding", + "enabled": true, + "learn": true + }, + { + "description": "IIS Unicode codepoints", + "enabled": true, + "learn": true + }, + { + "description": "IIS backslashes", + "enabled": true, + "learn": true + }, + { + "description": "%u decoding", + "enabled": true, + "learn": true + }, + { + "description": "Multiple decoding", + "enabled": true, + "learn": true, + "maxDecodingPasses": 2 + }, + { + "description": "Directory traversals", + "enabled": true, + "learn": true + }, + { + "description": "Multiple slashes", + "enabled": false, + "learn": true + } + ], + "http-protocols": [ + { + "description": "Check maximum number of cookies", + "enabled": false, + "learn": true, + "maxCookies": 50 + }, + { + "description": "Unescaped space in URL", + "enabled": false, + "learn": true + }, + { + "description": "Multiple host headers", + "enabled": true, + "learn": true + }, + { + "description": "Check maximum number of parameters", + "enabled": true, + "learn": true, + "maxParams": 100 + }, + { + "description": "Bad host header value", + "enabled": true, + "learn": true + }, + { + "description": "Check maximum number of headers", + "enabled": true, + "learn": true, + "maxHeaders": 100 + }, + { + "description": "Unparsable request content", + "enabled": true + }, + { + "description": "High ASCII characters in headers", + "enabled": true, + "learn": true + }, + { + "description": "Null in request", + "enabled": true + }, + { + "description": "Bad HTTP version", + "enabled": true + }, + { + "description": "Content length should be a positive number", + "enabled": true, + "learn": true + }, + { + "description": "Host header contains IP address", + "enabled": false, + "learn": true + }, + { + "description": "CRLF characters before request start", + "enabled": true, + "learn": true + }, + { + "description": "No Host header in HTTP/1.1 request", + "enabled": true, + "learn": true + }, + { + "description": "Bad multipart parameters parsing", + "enabled": true, + "learn": true + }, + { + "description": "Bad multipart/form-data request parsing", + "enabled": true, + "learn": false + }, + { + "description": "Body in GET or HEAD requests", + "enabled": false, + "learn": false + }, + { + "description": "Chunked request with Content-Length header", + "enabled": true, + "learn": true + }, + { + "description": "Several Content-Length headers", + "enabled": true, + "learn": true + }, + { + "description": "Header name with no header value", + "enabled": true, + "learn": true + }, + { + "description": "POST request with Content-Length: 0", + "enabled": false, + "learn": true + } + ], + "violations": [ + { + "alarm": true, + "block": true, + "description": "Failed to convert character", + "name": "VIOL_ENCODING" + }, + { + "alarm": true, + "block": false, + "description": "Illegal redirection attempt", + "learn": true, + "name": "VIOL_REDIRECT" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter", + "learn": true, + "name": "VIOL_PARAMETER" + }, + { + "alarm": true, + "block": false, + "description": "Illegal cross-origin request", + "learn": true, + "name": "VIOL_CROSS_ORIGIN_REQUEST" + }, + { + "alarm": true, + "block": true, + "description": "Brute Force: Maximum login attempts are exceeded", + "name": "VIOL_BRUTE_FORCE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal URL length", + "learn": true, + "name": "VIOL_URL_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "Login URL bypassed", + "learn": true, + "name": "VIOL_LOGIN_URL_BYPASSED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter data type", + "learn": true, + "name": "VIOL_PARAMETER_DATA_TYPE" + }, + { + "alarm": false, + "block": false, + "description": "Unauthorized access attempt", + "name": "VIOL_ACCESS_UNAUTHORIZED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal attachment in SOAP message", + "learn": true, + "name": "VIOL_XML_SOAP_ATTACHMENT" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in parameter name", + "learn": true, + "name": "VIOL_PARAMETER_NAME_METACHAR" + }, + { + "alarm": true, + "block": false, + "description": "Host name mismatch", + "name": "VIOL_HOSTNAME_MISMATCH" + }, + { + "alarm": false, + "block": false, + "description": "Missing Access Token", + "name": "VIOL_ACCESS_MISSING" + }, + { + "alarm": false, + "block": false, + "description": "Illegal session ID in URL", + "learn": true, + "name": "VIOL_DYNAMIC_SESSION" + }, + { + "alarm": true, + "block": true, + "description": "GraphQL disallowed pattern in response", + "name": "VIOL_GRAPHQL_ERROR_RESPONSE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal dynamic parameter value", + "learn": true, + "name": "VIOL_PARAMETER_DYNAMIC_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Data Guard: Information leakage detected", + "learn": true, + "name": "VIOL_DATA_GUARD" + }, + { + "alarm": true, + "block": true, + "description": "Malformed JSON data", + "learn": true, + "name": "VIOL_JSON_MALFORMED" + }, + { + "alarm": true, + "block": true, + "description": "Leaked Credentials Detection", + "name": "VIOL_LEAKED_CREDENTIALS" + }, + { + "alarm": true, + "block": false, + "description": "Illegal cookie length", + "learn": true, + "name": "VIOL_COOKIE_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "Illegal query string length", + "learn": true, + "name": "VIOL_QUERY_STRING_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "SOAP method not allowed", + "learn": true, + "name": "VIOL_XML_SOAP_METHOD" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter numeric value", + "learn": true, + "name": "VIOL_PARAMETER_NUMERIC_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal repeated header", + "learn": true, + "name": "VIOL_HEADER_REPEATED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal empty parameter value", + "learn": true, + "name": "VIOL_PARAMETER_EMPTY_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal file type", + "learn": true, + "name": "VIOL_FILETYPE" + }, + { + "alarm": true, + "block": false, + "description": "Access from disallowed User/Session/IP/Device ID", + "name": "VIOL_SESSION_AWARENESS" + }, + { + "alarm": true, + "block": false, + "description": "Login URL expired", + "learn": true, + "name": "VIOL_LOGIN_URL_EXPIRED" + }, + { + "alarm": true, + "block": false, + "description": "XML data does not comply with format settings", + "learn": true, + "name": "VIOL_XML_FORMAT" + }, + { + "alarm": false, + "block": false, + "description": "Mandatory parameter is missing", + "name": "VIOL_MANDATORY_PARAMETER" + }, + { + "alarm": true, + "block": false, + "description": "Illegal request content type", + "learn": true, + "name": "VIOL_URL_CONTENT_TYPE" + }, + { + "alarm": true, + "block": true, + "description": "Bot Client Detected", + "learn": true, + "name": "VIOL_BOT_CLIENT" + }, + { + "alarm": true, + "block": false, + "description": "HTTP protocol compliance failed", + "learn": true, + "name": "VIOL_HTTP_PROTOCOL" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter array value", + "name": "VIOL_PARAMETER_ARRAY_VALUE" + }, + { + "alarm": false, + "block": false, + "description": "GWT data does not comply with format settings", + "learn": true, + "name": "VIOL_GWT_FORMAT" + }, + { + "alarm": true, + "block": true, + "description": "IP is blacklisted", + "name": "VIOL_BLACKLISTED_IP" + }, + { + "alarm": true, + "block": true, + "description": "Malformed XML data", + "learn": true, + "name": "VIOL_XML_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "Null in multi-part parameter value", + "learn": true, + "name": "VIOL_PARAMETER_MULTIPART_NULL_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "Server-side access to disallowed host", + "name": "VIOL_SERVER_SIDE_HOST" + }, + { + "alarm": false, + "block": false, + "description": "Expired timestamp", + "learn": true, + "name": "VIOL_COOKIE_EXPIRED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in value", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_METACHAR" + }, + { + "alarm": false, + "block": false, + "description": "Illegal flow to URL", + "learn": true, + "name": "VIOL_FLOW" + }, + { + "alarm": true, + "block": false, + "description": "CSRF attack detected", + "name": "VIOL_CSRF" + }, + { + "alarm": false, + "block": false, + "description": "Illegal entry point", + "learn": true, + "name": "VIOL_FLOW_ENTRY_POINT" + }, + { + "alarm": true, + "block": false, + "description": "Mandatory HTTP header is missing", + "learn": true, + "name": "VIOL_MANDATORY_HEADER" + }, + { + "alarm": true, + "block": false, + "description": "Parameter value does not comply with regular expression", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_REGEXP" + }, + { + "alarm": false, + "block": false, + "description": "Illegal query string or POST data", + "learn": true, + "name": "VIOL_FLOW_DISALLOWED_INPUT" + }, + { + "alarm": true, + "block": false, + "description": "JSON data does not comply with JSON schema", + "name": "VIOL_JSON_SCHEMA" + }, + { + "alarm": true, + "block": false, + "description": "Illegal repeated parameter name", + "learn": true, + "name": "VIOL_PARAMETER_REPEATED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter value length", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_LENGTH" + }, + { + "alarm": true, + "block": false, + "description": "Web Services Security failure", + "learn": true, + "name": "VIOL_XML_WEB_SERVICES_SECURITY" + }, + { + "alarm": true, + "block": false, + "description": "GraphQL data does not comply with format settings", + "learn": true, + "name": "VIOL_GRAPHQL_FORMAT" + }, + { + "alarm": false, + "block": false, + "description": "Virus detected", + "learn": true, + "name": "VIOL_VIRUS" + }, + { + "alarm": true, + "block": false, + "description": "Illegal header length", + "learn": true, + "name": "VIOL_HEADER_LENGTH" + }, + { + "alarm": false, + "block": false, + "description": "Malformed GWT data", + "learn": true, + "name": "VIOL_GWT_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal parameter location", + "name": "VIOL_PARAMETER_LOCATION" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in header", + "learn": true, + "name": "VIOL_HEADER_METACHAR" + }, + { + "alarm": true, + "block": true, + "description": "GraphQL introspection query", + "name": "VIOL_GRAPHQL_INTROSPECTION_QUERY" + }, + { + "alarm": true, + "block": false, + "description": "Evasion technique detected", + "learn": true, + "name": "VIOL_EVASION" + }, + { + "alarm": true, + "block": true, + "description": "Malformed GraphQL data", + "learn": true, + "name": "VIOL_GRAPHQL_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "JSON data does not comply with format settings", + "learn": true, + "name": "VIOL_JSON_FORMAT" + }, + { + "alarm": true, + "block": false, + "description": "Disallowed file upload content detected", + "learn": true, + "name": "VIOL_FILE_UPLOAD" + }, + { + "alarm": true, + "block": true, + "description": "Mitigation action determined by Threat Analysis Platform", + "name": "VIOL_THREAT_ANALYSIS" + }, + { + "alarm": true, + "block": false, + "description": "Illegal static parameter value", + "learn": true, + "name": "VIOL_PARAMETER_STATIC_VALUE" + }, + { + "alarm": true, + "block": false, + "description": "XML data does not comply with schema or WSDL document", + "learn": true, + "name": "VIOL_XML_SCHEMA" + }, + { + "alarm": true, + "block": true, + "description": "Unsupported browser", + "learn": true, + "name": "VIOL_BROWSER" + }, + { + "alarm": true, + "block": false, + "description": "Illegal method", + "learn": true, + "name": "VIOL_METHOD" + }, + { + "alarm": true, + "block": false, + "description": "Illegal Base64 value", + "learn": true, + "name": "VIOL_PARAMETER_VALUE_BASE64" + }, + { + "alarm": true, + "block": true, + "description": "ASM Cookie Hijacking", + "learn": true, + "name": "VIOL_ASM_COOKIE_HIJACKING" + }, + { + "alarm": true, + "block": false, + "description": "DataSafe Data Integrity", + "name": "VIOL_DATA_INTEGRITY" + }, + { + "alarm": true, + "block": false, + "description": "Illegal URL", + "learn": true, + "name": "VIOL_URL" + }, + { + "alarm": true, + "block": true, + "description": "Threat Campaign detected", + "name": "VIOL_THREAT_CAMPAIGN" + }, + { + "description": "Attack signature detected", + "name": "VIOL_ATTACK_SIGNATURE" + }, + { + "alarm": true, + "block": false, + "description": "Illegal meta character in URL", + "learn": true, + "name": "VIOL_URL_METACHAR" + }, + { + "alarm": true, + "block": true, + "description": "Access from malicious IP address", + "name": "VIOL_MALICIOUS_IP" + }, + { + "alarm": true, + "block": true, + "description": "Request length exceeds defined buffer size", + "learn": true, + "name": "VIOL_REQUEST_MAX_LENGTH" + }, + { + "alarm": false, + "block": false, + "description": "Bad Actor Detected", + "name": "VIOL_MALICIOUS_DEVICE" + }, + { + "alarm": true, + "block": true, + "description": "Modified WAF cookie", + "learn": true, + "name": "VIOL_ASM_COOKIE_MODIFIED" + }, + { + "alarm": true, + "block": false, + "description": "Violation Rating Need Examination detected", + "name": "VIOL_RATING_NEED_EXAMINATION" + }, + { + "alarm": true, + "block": false, + "description": "CSRF authentication expired", + "learn": true, + "name": "VIOL_CSRF_EXPIRED" + }, + { + "alarm": true, + "block": false, + "description": "Illegal request length", + "learn": true, + "name": "VIOL_REQUEST_LENGTH" + }, + { + "alarm": true, + "block": true, + "description": "Blocking Condition Detected", + "name": "VIOL_BLOCKING_CONDITION" + }, + { + "alarm": true, + "block": false, + "description": "Modified domain cookie(s)", + "learn": true, + "name": "VIOL_COOKIE_MODIFIED" + }, + { + "alarm": true, + "block": false, + "description": "Mandatory request body is missing", + "name": "VIOL_MANDATORY_REQUEST_BODY" + }, + { + "alarm": false, + "block": false, + "description": "Access token does not comply with the profile requirements", + "name": "VIOL_ACCESS_INVALID" + }, + { + "alarm": false, + "block": false, + "description": "Illegal host name", + "name": "VIOL_HOSTNAME" + }, + { + "alarm": true, + "block": true, + "description": "Cookie not RFC-compliant", + "learn": true, + "name": "VIOL_COOKIE_MALFORMED" + }, + { + "alarm": true, + "block": false, + "description": "Disallowed file upload content detected in body", + "name": "VIOL_FILE_UPLOAD_IN_BODY" + }, + { + "alarm": true, + "block": false, + "description": "Illegal POST data length", + "learn": true, + "name": "VIOL_POST_DATA_LENGTH" + }, + { + "alarm": true, + "block": true, + "description": "Access from disallowed Geolocation", + "learn": true, + "name": "VIOL_GEOLOCATION" + }, + { + "alarm": false, + "block": false, + "description": "Illegal number of mandatory parameters", + "learn": true, + "name": "VIOL_FLOW_MANDATORY_PARAMS" + }, + { + "alarm": true, + "block": true, + "description": "Violation Rating Threat detected", + "name": "VIOL_RATING_THREAT" + }, + { + "alarm": true, + "block": true, + "description": "Illegal HTTP status in response", + "learn": true, + "name": "VIOL_HTTP_RESPONSE_STATUS" + }, + { + "alarm": false, + "block": false, + "description": "Malformed Access Token", + "name": "VIOL_ACCESS_MALFORMED" + } + ], + "web-services-securities": [ + { + "description": "UnSigned Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Timestamp expiration is too far in the future", + "enabled": false, + "learn": true + }, + { + "description": "Expired Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Invalid Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Missing Timestamp", + "enabled": false, + "learn": true + }, + { + "description": "Verification Error", + "enabled": false, + "learn": true + }, + { + "description": "Signing Error", + "enabled": false, + "learn": true + }, + { + "description": "Encryption Error", + "enabled": false, + "learn": true + }, + { + "description": "Decryption Error", + "enabled": false, + "learn": true + }, + { + "description": "Certificate Error", + "enabled": false, + "learn": true + }, + { + "description": "Certificate Expired", + "enabled": false, + "learn": true + }, + { + "description": "Malformed Error", + "enabled": false, + "learn": true + }, + { + "description": "Internal Error", + "enabled": false, + "learn": true + } + ] + }, + "bot-defense": { + "mitigations": { + "classes": [ + { + "action": "block", + "name": "malicious-bot" + }, + { + "action": "ignore", + "name": "suspicious-browser" + }, + { + "action": "ignore", + "name": "untrusted-bot" + }, + { + "action": "ignore", + "name": "trusted-bot" + }, + { + "action": "detect", + "name": "browser" + }, + { + "action": "ignore", + "name": "unknown" + } + ] + }, + "settings": { + "caseSensitiveHttpHeaders": true, + "isEnabled": true + } + }, + "brute-force-attack-preventions": [ + { + "bruteForceProtectionForAllLoginPages": false, + "captchaBypassCriteria": { + "action": "alarm-and-drop", + "enabled": false, + "threshold": 5 + }, + "clientSideIntegrityBypassCriteria": { + "action": "alarm-and-captcha", + "threshold": 3 + }, + "detectionCriteria": { + "action": "alarm", + "credentialsStuffingMatchesReached": 100, + "failedLoginAttemptsRateReached": 100 + }, + "leakedCredentialsCriteria": { + "action": "alarm-and-blocking-page" + }, + "loginAttemptsFromTheSameDeviceId": { + "action": "alarm", + "threshold": 3 + }, + "loginAttemptsFromTheSameIp": { + "action": "alarm-and-blocking-page", + "enabled": true, + "threshold": 20 + }, + "loginAttemptsFromTheSameUser": { + "action": "alarm", + "enabled": true, + "threshold": 3 + }, + "measurementPeriod": 900, + "preventionDuration": "3600", + "reEnableLoginAfter": 3600, + "sourceBasedProtectionDetectionPeriod": 3600 + } + ], + "caseInsensitive": true, + "character-sets": [ + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": true, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": true, + "metachar": "0x24" + }, + { + "isAllowed": true, + "metachar": "0x25" + }, + { + "isAllowed": true, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": true, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": true, + "metachar": "0x3a" + }, + { + "isAllowed": true, + "metachar": "0x3b" + }, + { + "isAllowed": true, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": true, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": true, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": true, + "metachar": "0x5b" + }, + { + "isAllowed": true, + "metachar": "0x5c" + }, + { + "isAllowed": true, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": true, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": true, + "metachar": "0x7d" + }, + { + "isAllowed": true, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + }, + { + "isAllowed": false, + "metachar": "0x80" + }, + { + "isAllowed": false, + "metachar": "0x81" + }, + { + "isAllowed": false, + "metachar": "0x82" + }, + { + "isAllowed": false, + "metachar": "0x83" + }, + { + "isAllowed": false, + "metachar": "0x84" + }, + { + "isAllowed": false, + "metachar": "0x85" + }, + { + "isAllowed": false, + "metachar": "0x86" + }, + { + "isAllowed": false, + "metachar": "0x87" + }, + { + "isAllowed": false, + "metachar": "0x88" + }, + { + "isAllowed": false, + "metachar": "0x89" + }, + { + "isAllowed": false, + "metachar": "0x8a" + }, + { + "isAllowed": false, + "metachar": "0x8b" + }, + { + "isAllowed": false, + "metachar": "0x8c" + }, + { + "isAllowed": false, + "metachar": "0x8d" + }, + { + "isAllowed": false, + "metachar": "0x8e" + }, + { + "isAllowed": false, + "metachar": "0x8f" + }, + { + "isAllowed": false, + "metachar": "0x90" + }, + { + "isAllowed": false, + "metachar": "0x91" + }, + { + "isAllowed": false, + "metachar": "0x92" + }, + { + "isAllowed": false, + "metachar": "0x93" + }, + { + "isAllowed": false, + "metachar": "0x94" + }, + { + "isAllowed": false, + "metachar": "0x95" + }, + { + "isAllowed": false, + "metachar": "0x96" + }, + { + "isAllowed": false, + "metachar": "0x97" + }, + { + "isAllowed": false, + "metachar": "0x98" + }, + { + "isAllowed": false, + "metachar": "0x99" + }, + { + "isAllowed": false, + "metachar": "0x9a" + }, + { + "isAllowed": false, + "metachar": "0x9b" + }, + { + "isAllowed": false, + "metachar": "0x9c" + }, + { + "isAllowed": false, + "metachar": "0x9d" + }, + { + "isAllowed": false, + "metachar": "0x9e" + }, + { + "isAllowed": false, + "metachar": "0x9f" + }, + { + "isAllowed": false, + "metachar": "0xa0" + }, + { + "isAllowed": false, + "metachar": "0xa1" + }, + { + "isAllowed": false, + "metachar": "0xa2" + }, + { + "isAllowed": false, + "metachar": "0xa3" + }, + { + "isAllowed": false, + "metachar": "0xa4" + }, + { + "isAllowed": false, + "metachar": "0xa5" + }, + { + "isAllowed": false, + "metachar": "0xa6" + }, + { + "isAllowed": false, + "metachar": "0xa7" + }, + { + "isAllowed": false, + "metachar": "0xa8" + }, + { + "isAllowed": false, + "metachar": "0xa9" + }, + { + "isAllowed": false, + "metachar": "0xaa" + }, + { + "isAllowed": false, + "metachar": "0xab" + }, + { + "isAllowed": false, + "metachar": "0xac" + }, + { + "isAllowed": false, + "metachar": "0xad" + }, + { + "isAllowed": false, + "metachar": "0xae" + }, + { + "isAllowed": false, + "metachar": "0xaf" + }, + { + "isAllowed": false, + "metachar": "0xb0" + }, + { + "isAllowed": false, + "metachar": "0xb1" + }, + { + "isAllowed": false, + "metachar": "0xb2" + }, + { + "isAllowed": false, + "metachar": "0xb3" + }, + { + "isAllowed": false, + "metachar": "0xb4" + }, + { + "isAllowed": false, + "metachar": "0xb5" + }, + { + "isAllowed": false, + "metachar": "0xb6" + }, + { + "isAllowed": false, + "metachar": "0xb7" + }, + { + "isAllowed": false, + "metachar": "0xb8" + }, + { + "isAllowed": false, + "metachar": "0xb9" + }, + { + "isAllowed": false, + "metachar": "0xba" + }, + { + "isAllowed": false, + "metachar": "0xbb" + }, + { + "isAllowed": false, + "metachar": "0xbc" + }, + { + "isAllowed": false, + "metachar": "0xbd" + }, + { + "isAllowed": false, + "metachar": "0xbe" + }, + { + "isAllowed": false, + "metachar": "0xbf" + }, + { + "isAllowed": true, + "metachar": "0xc0" + }, + { + "isAllowed": true, + "metachar": "0xc1" + }, + { + "isAllowed": true, + "metachar": "0xc2" + }, + { + "isAllowed": true, + "metachar": "0xc3" + }, + { + "isAllowed": true, + "metachar": "0xc4" + }, + { + "isAllowed": true, + "metachar": "0xc5" + }, + { + "isAllowed": true, + "metachar": "0xc6" + }, + { + "isAllowed": true, + "metachar": "0xc7" + }, + { + "isAllowed": true, + "metachar": "0xc8" + }, + { + "isAllowed": true, + "metachar": "0xc9" + }, + { + "isAllowed": true, + "metachar": "0xca" + }, + { + "isAllowed": true, + "metachar": "0xcb" + }, + { + "isAllowed": true, + "metachar": "0xcc" + }, + { + "isAllowed": true, + "metachar": "0xcd" + }, + { + "isAllowed": true, + "metachar": "0xce" + }, + { + "isAllowed": true, + "metachar": "0xcf" + }, + { + "isAllowed": true, + "metachar": "0xd0" + }, + { + "isAllowed": true, + "metachar": "0xd1" + }, + { + "isAllowed": true, + "metachar": "0xd2" + }, + { + "isAllowed": true, + "metachar": "0xd3" + }, + { + "isAllowed": true, + "metachar": "0xd4" + }, + { + "isAllowed": true, + "metachar": "0xd5" + }, + { + "isAllowed": true, + "metachar": "0xd6" + }, + { + "isAllowed": false, + "metachar": "0xd7" + }, + { + "isAllowed": true, + "metachar": "0xd8" + }, + { + "isAllowed": true, + "metachar": "0xd9" + }, + { + "isAllowed": true, + "metachar": "0xda" + }, + { + "isAllowed": true, + "metachar": "0xdb" + }, + { + "isAllowed": true, + "metachar": "0xdc" + }, + { + "isAllowed": true, + "metachar": "0xdd" + }, + { + "isAllowed": true, + "metachar": "0xde" + }, + { + "isAllowed": true, + "metachar": "0xdf" + }, + { + "isAllowed": true, + "metachar": "0xe0" + }, + { + "isAllowed": true, + "metachar": "0xe1" + }, + { + "isAllowed": true, + "metachar": "0xe2" + }, + { + "isAllowed": true, + "metachar": "0xe3" + }, + { + "isAllowed": true, + "metachar": "0xe4" + }, + { + "isAllowed": true, + "metachar": "0xe5" + }, + { + "isAllowed": true, + "metachar": "0xe6" + }, + { + "isAllowed": true, + "metachar": "0xe7" + }, + { + "isAllowed": true, + "metachar": "0xe8" + }, + { + "isAllowed": true, + "metachar": "0xe9" + }, + { + "isAllowed": true, + "metachar": "0xea" + }, + { + "isAllowed": true, + "metachar": "0xeb" + }, + { + "isAllowed": true, + "metachar": "0xec" + }, + { + "isAllowed": true, + "metachar": "0xed" + }, + { + "isAllowed": true, + "metachar": "0xee" + }, + { + "isAllowed": true, + "metachar": "0xef" + }, + { + "isAllowed": true, + "metachar": "0xf0" + }, + { + "isAllowed": true, + "metachar": "0xf1" + }, + { + "isAllowed": true, + "metachar": "0xf2" + }, + { + "isAllowed": true, + "metachar": "0xf3" + }, + { + "isAllowed": true, + "metachar": "0xf4" + }, + { + "isAllowed": true, + "metachar": "0xf5" + }, + { + "isAllowed": true, + "metachar": "0xf6" + }, + { + "isAllowed": false, + "metachar": "0xf7" + }, + { + "isAllowed": true, + "metachar": "0xf8" + }, + { + "isAllowed": true, + "metachar": "0xf9" + }, + { + "isAllowed": true, + "metachar": "0xfa" + }, + { + "isAllowed": true, + "metachar": "0xfb" + }, + { + "isAllowed": true, + "metachar": "0xfc" + }, + { + "isAllowed": true, + "metachar": "0xfd" + }, + { + "isAllowed": true, + "metachar": "0xfe" + }, + { + "isAllowed": true, + "metachar": "0xff" + } + ], + "characterSetType": "header" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": true, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": false, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "url" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": false, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "parameter-name" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": false, + "metachar": "0x28" + }, + { + "isAllowed": false, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": false, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "parameter-value" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": true, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": true, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": false, + "metachar": "0x28" + }, + { + "isAllowed": false, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": false, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "xml-content" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": false, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": false, + "metachar": "0x23" + }, + { + "isAllowed": false, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": false, + "metachar": "0x26" + }, + { + "isAllowed": false, + "metachar": "0x27" + }, + { + "isAllowed": false, + "metachar": "0x28" + }, + { + "isAllowed": false, + "metachar": "0x29" + }, + { + "isAllowed": false, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": false, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": false, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": false, + "metachar": "0x3a" + }, + { + "isAllowed": false, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": false, + "metachar": "0x3f" + }, + { + "isAllowed": false, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": false, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": false, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": false, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": false, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "json-content" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": false, + "metachar": "0x20" + }, + { + "isAllowed": true, + "metachar": "0x21" + }, + { + "isAllowed": false, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": true, + "metachar": "0x24" + }, + { + "isAllowed": false, + "metachar": "0x25" + }, + { + "isAllowed": true, + "metachar": "0x26" + }, + { + "isAllowed": true, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": true, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": true, + "metachar": "0x3a" + }, + { + "isAllowed": true, + "metachar": "0x3b" + }, + { + "isAllowed": false, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": false, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": true, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": true, + "metachar": "0x5b" + }, + { + "isAllowed": false, + "metachar": "0x5c" + }, + { + "isAllowed": true, + "metachar": "0x5d" + }, + { + "isAllowed": false, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": false, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": false, + "metachar": "0x7b" + }, + { + "isAllowed": true, + "metachar": "0x7c" + }, + { + "isAllowed": false, + "metachar": "0x7d" + }, + { + "isAllowed": true, + "metachar": "0x7e" + }, + { + "isAllowed": false, + "metachar": "0x7f" + } + ], + "characterSetType": "gwt-content" + }, + { + "characterSet": [ + { + "isAllowed": false, + "metachar": "0x0" + }, + { + "isAllowed": false, + "metachar": "0x1" + }, + { + "isAllowed": false, + "metachar": "0x2" + }, + { + "isAllowed": false, + "metachar": "0x3" + }, + { + "isAllowed": false, + "metachar": "0x4" + }, + { + "isAllowed": false, + "metachar": "0x5" + }, + { + "isAllowed": false, + "metachar": "0x6" + }, + { + "isAllowed": false, + "metachar": "0x7" + }, + { + "isAllowed": false, + "metachar": "0x8" + }, + { + "isAllowed": false, + "metachar": "0x9" + }, + { + "isAllowed": false, + "metachar": "0xa" + }, + { + "isAllowed": false, + "metachar": "0xb" + }, + { + "isAllowed": false, + "metachar": "0xc" + }, + { + "isAllowed": false, + "metachar": "0xd" + }, + { + "isAllowed": false, + "metachar": "0xe" + }, + { + "isAllowed": false, + "metachar": "0xf" + }, + { + "isAllowed": false, + "metachar": "0x10" + }, + { + "isAllowed": false, + "metachar": "0x11" + }, + { + "isAllowed": false, + "metachar": "0x12" + }, + { + "isAllowed": false, + "metachar": "0x13" + }, + { + "isAllowed": false, + "metachar": "0x14" + }, + { + "isAllowed": false, + "metachar": "0x15" + }, + { + "isAllowed": false, + "metachar": "0x16" + }, + { + "isAllowed": false, + "metachar": "0x17" + }, + { + "isAllowed": false, + "metachar": "0x18" + }, + { + "isAllowed": false, + "metachar": "0x19" + }, + { + "isAllowed": false, + "metachar": "0x1a" + }, + { + "isAllowed": false, + "metachar": "0x1b" + }, + { + "isAllowed": false, + "metachar": "0x1c" + }, + { + "isAllowed": false, + "metachar": "0x1d" + }, + { + "isAllowed": false, + "metachar": "0x1e" + }, + { + "isAllowed": false, + "metachar": "0x1f" + }, + { + "isAllowed": true, + "metachar": "0x20" + }, + { + "isAllowed": true, + "metachar": "0x21" + }, + { + "isAllowed": true, + "metachar": "0x22" + }, + { + "isAllowed": true, + "metachar": "0x23" + }, + { + "isAllowed": true, + "metachar": "0x24" + }, + { + "isAllowed": true, + "metachar": "0x25" + }, + { + "isAllowed": true, + "metachar": "0x26" + }, + { + "isAllowed": true, + "metachar": "0x27" + }, + { + "isAllowed": true, + "metachar": "0x28" + }, + { + "isAllowed": true, + "metachar": "0x29" + }, + { + "isAllowed": true, + "metachar": "0x2a" + }, + { + "isAllowed": true, + "metachar": "0x2b" + }, + { + "isAllowed": true, + "metachar": "0x2c" + }, + { + "isAllowed": true, + "metachar": "0x2d" + }, + { + "isAllowed": true, + "metachar": "0x2e" + }, + { + "isAllowed": true, + "metachar": "0x2f" + }, + { + "isAllowed": true, + "metachar": "0x30" + }, + { + "isAllowed": true, + "metachar": "0x31" + }, + { + "isAllowed": true, + "metachar": "0x32" + }, + { + "isAllowed": true, + "metachar": "0x33" + }, + { + "isAllowed": true, + "metachar": "0x34" + }, + { + "isAllowed": true, + "metachar": "0x35" + }, + { + "isAllowed": true, + "metachar": "0x36" + }, + { + "isAllowed": true, + "metachar": "0x37" + }, + { + "isAllowed": true, + "metachar": "0x38" + }, + { + "isAllowed": true, + "metachar": "0x39" + }, + { + "isAllowed": true, + "metachar": "0x3a" + }, + { + "isAllowed": true, + "metachar": "0x3b" + }, + { + "isAllowed": true, + "metachar": "0x3c" + }, + { + "isAllowed": true, + "metachar": "0x3d" + }, + { + "isAllowed": true, + "metachar": "0x3e" + }, + { + "isAllowed": true, + "metachar": "0x3f" + }, + { + "isAllowed": true, + "metachar": "0x40" + }, + { + "isAllowed": true, + "metachar": "0x41" + }, + { + "isAllowed": true, + "metachar": "0x42" + }, + { + "isAllowed": true, + "metachar": "0x43" + }, + { + "isAllowed": true, + "metachar": "0x44" + }, + { + "isAllowed": true, + "metachar": "0x45" + }, + { + "isAllowed": true, + "metachar": "0x46" + }, + { + "isAllowed": true, + "metachar": "0x47" + }, + { + "isAllowed": true, + "metachar": "0x48" + }, + { + "isAllowed": true, + "metachar": "0x49" + }, + { + "isAllowed": true, + "metachar": "0x4a" + }, + { + "isAllowed": true, + "metachar": "0x4b" + }, + { + "isAllowed": true, + "metachar": "0x4c" + }, + { + "isAllowed": true, + "metachar": "0x4d" + }, + { + "isAllowed": true, + "metachar": "0x4e" + }, + { + "isAllowed": true, + "metachar": "0x4f" + }, + { + "isAllowed": true, + "metachar": "0x50" + }, + { + "isAllowed": true, + "metachar": "0x51" + }, + { + "isAllowed": true, + "metachar": "0x52" + }, + { + "isAllowed": true, + "metachar": "0x53" + }, + { + "isAllowed": true, + "metachar": "0x54" + }, + { + "isAllowed": true, + "metachar": "0x55" + }, + { + "isAllowed": true, + "metachar": "0x56" + }, + { + "isAllowed": true, + "metachar": "0x57" + }, + { + "isAllowed": true, + "metachar": "0x58" + }, + { + "isAllowed": true, + "metachar": "0x59" + }, + { + "isAllowed": true, + "metachar": "0x5a" + }, + { + "isAllowed": true, + "metachar": "0x5b" + }, + { + "isAllowed": true, + "metachar": "0x5c" + }, + { + "isAllowed": true, + "metachar": "0x5d" + }, + { + "isAllowed": true, + "metachar": "0x5e" + }, + { + "isAllowed": true, + "metachar": "0x5f" + }, + { + "isAllowed": true, + "metachar": "0x60" + }, + { + "isAllowed": true, + "metachar": "0x61" + }, + { + "isAllowed": true, + "metachar": "0x62" + }, + { + "isAllowed": true, + "metachar": "0x63" + }, + { + "isAllowed": true, + "metachar": "0x64" + }, + { + "isAllowed": true, + "metachar": "0x65" + }, + { + "isAllowed": true, + "metachar": "0x66" + }, + { + "isAllowed": true, + "metachar": "0x67" + }, + { + "isAllowed": true, + "metachar": "0x68" + }, + { + "isAllowed": true, + "metachar": "0x69" + }, + { + "isAllowed": true, + "metachar": "0x6a" + }, + { + "isAllowed": true, + "metachar": "0x6b" + }, + { + "isAllowed": true, + "metachar": "0x6c" + }, + { + "isAllowed": true, + "metachar": "0x6d" + }, + { + "isAllowed": true, + "metachar": "0x6e" + }, + { + "isAllowed": true, + "metachar": "0x6f" + }, + { + "isAllowed": true, + "metachar": "0x70" + }, + { + "isAllowed": true, + "metachar": "0x71" + }, + { + "isAllowed": true, + "metachar": "0x72" + }, + { + "isAllowed": true, + "metachar": "0x73" + }, + { + "isAllowed": true, + "metachar": "0x74" + }, + { + "isAllowed": true, + "metachar": "0x75" + }, + { + "isAllowed": true, + "metachar": "0x76" + }, + { + "isAllowed": true, + "metachar": "0x77" + }, + { + "isAllowed": true, + "metachar": "0x78" + }, + { + "isAllowed": true, + "metachar": "0x79" + }, + { + "isAllowed": true, + "metachar": "0x7a" + }, + { + "isAllowed": true, + "metachar": "0x7b" + }, + { + "isAllowed": true, + "metachar": "0x7c" + }, + { + "isAllowed": true, + "metachar": "0x7d" + }, + { + "isAllowed": true, + "metachar": "0x7e" + }, + { + "isAllowed": true, + "metachar": "0x7f" + } + ], + "characterSetType": "plain-text-content" + } + ], + "cookie-settings": { + "maximumCookieHeaderLength": "4096" + }, + "cookies": [ + { + "accessibleOnlyThroughTheHttpProtocol": false, + "attackSignaturesCheck": true, + "enforcementType": "allow", + "insertSameSiteAttribute": "lax", + "isBase64": false, + "maskValueInLogs": false, + "name": "*", + "performStaging": false, + "securedOverHttpsConnection": true, + "type": "wildcard", + "wildcardOrder": 1 + } + ], + "csrf-protection": { + "enabled": false + }, + "csrf-urls": [ + { + "enforcementAction": "verify-origin", + "method": "POST", + "requiredParameters": "ignore", + "url": "*", + "wildcardOrder": 1 + } + ], + "data-guard": { + "enabled": false, + "enforcementMode": "ignore-urls-in-list" + }, + "description": "new_waf_policy desc updated", + "dos-protection": { + "behavioral-dos": { + "badActorDetection": { + "enableTlsIndexing": true, + "enabled": true + }, + "enableHttpSignatures": true, + "enableTlsSignatures": false, + "mitigationLevel": "standard" + }, + "enabled": false + }, + "enablePassiveMode": false, + "enforcementMode": "blocking", + "filetypes": [ + { + "allowed": false, + "name": "bat" + }, + { + "allowed": false, + "name": "sav" + }, + { + "allowed": false, + "name": "pfx" + }, + { + "allowed": false, + "name": "eml" + }, + { + "allowed": false, + "name": "wmz" + }, + { + "allowed": false, + "name": "crt" + }, + { + "allowed": false, + "name": "nws" + }, + { + "allowed": false, + "name": "idq" + }, + { + "allowed": false, + "name": "conf" + }, + { + "allowed": false, + "name": "dat" + }, + { + "allowed": false, + "name": "msi" + }, + { + "allowed": false, + "name": "key" + }, + { + "allowed": false, + "name": "temp" + }, + { + "allowed": false, + "name": "idc" + }, + { + "allowed": false, + "name": "pol" + }, + { + "allowed": false, + "name": "cer" + }, + { + "allowed": false, + "name": "pem" + }, + { + "allowed": false, + "name": "ida" + }, + { + "allowed": false, + "name": "stm" + }, + { + "allowed": false, + "name": "log" + }, + { + "allowed": false, + "name": "sys" + }, + { + "allowed": false, + "name": "p7c" + }, + { + "allowed": false, + "name": "cgi" + }, + { + "allowed": false, + "name": "ini" + }, + { + "allowed": false, + "name": "shtm" + }, + { + "allowed": false, + "name": "cfg" + }, + { + "allowed": false, + "name": "dll" + }, + { + "allowed": false, + "name": "save" + }, + { + "allowed": false, + "name": "htr" + }, + { + "allowed": false, + "name": "hta" + }, + { + "allowed": false, + "name": "bck" + }, + { + "allowed": false, + "name": "exe" + }, + { + "allowed": false, + "name": "old" + }, + { + "allowed": false, + "name": "p7b" + }, + { + "allowed": false, + "name": "com" + }, + { + "allowed": false, + "name": "cmd" + }, + { + "allowed": false, + "name": "tmp" + }, + { + "allowed": false, + "name": "reg" + }, + { + "allowed": false, + "name": "der" + }, + { + "allowed": false, + "name": "htw" + }, + { + "allowed": false, + "name": "shtml" + }, + { + "allowed": false, + "name": "bkp" + }, + { + "allowed": false, + "name": "p12" + }, + { + "allowed": false, + "name": "bak" + }, + { + "allowed": false, + "name": "config" + }, + { + "allowed": false, + "name": "printer" + }, + { + "allowed": false, + "name": "bin" + }, + { + "allowed": true, + "checkPostDataLength": true, + "checkQueryStringLength": true, + "checkRequestLength": true, + "checkUrlLength": true, + "name": "*", + "performStaging": false, + "postDataLength": 4096, + "queryStringLength": 2048, + "requestLength": 8196, + "responseCheck": false, + "responseCheckLength": 20000, + "type": "wildcard", + "urlLength": 1024, + "wildcardOrder": 1 + } + ], + "fullPath": "/Common/Rating-Based-Template", + "general": { + "allowedResponseCodes": [ + 400, + 401, + 403, + 404, + 405, + 406, + 407, + 415, + 417, + 503 + ], + "enableEventCorrelation": true, + "enforcementReadinessPeriod": 7, + "maskCreditCardNumbersInRequest": true, + "pathParameterHandling": "as-parameters", + "triggerAsmIruleEvent": "disabled", + "trustXff": false, + "useDynamicSessionIdInUrl": false + }, + "graphql-profiles": [ + { + "attackSignaturesCheck": true, + "defenseAttributes": { + "allowIntrospectionQueries": false, + "maximumBatchedQueries": "any", + "maximumStructureDepth": 10, + "maximumTotalLength": "any", + "maximumValueLength": "any", + "tolerateParsingWarnings": false + }, + "description": "Default GraphQL Profile", + "hasIdlFiles": false, + "metacharElementCheck": true, + "name": "Default", + "responseEnforcement": { + "blockDisallowedPatterns": false + } + } + ], + "gwt-profiles": [ + { + "attackSignaturesCheck": true, + "defenseAttributes": { + "maximumTotalLengthOfGWTData": 10000, + "maximumValueLength": 100, + "tolerateGWTParsingWarnings": false + }, + "description": "Default GWT Profile", + "metacharElementCheck": true, + "name": "Default" + } + ], + "header-settings": { + "maximumHttpHeaderLength": "8192" + }, + "headers": [ + { + "allowRepeatedOccurrences": false, + "checkSignatures": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "cookie", + "type": "explicit" + }, + { + "allowRepeatedOccurrences": true, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "referer", + "normalizationViolations": true, + "percentDecoding": false, + "type": "explicit", + "urlNormalization": true + }, + { + "allowRepeatedOccurrences": false, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "transfer-encoding", + "normalizationViolations": false, + "percentDecoding": false, + "type": "explicit", + "urlNormalization": false + }, + { + "allowRepeatedOccurrences": false, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": true, + "name": "authorization", + "normalizationViolations": false, + "percentDecoding": true, + "type": "explicit", + "urlNormalization": false + }, + { + "allowRepeatedOccurrences": true, + "base64Decoding": false, + "checkSignatures": true, + "htmlNormalization": false, + "mandatory": false, + "maskValueInLogs": false, + "name": "*", + "normalizationViolations": false, + "percentDecoding": true, + "type": "wildcard", + "urlNormalization": false, + "wildcardOrder": 1 + } + ], + "ip-intelligence": { + "enabled": true, + "ipIntelligenceCategories": [ + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Spam Sources" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Windows Exploits" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Web Attacks" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "BotNets" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Scanners" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Denial of Service" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Infected Sources" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Phishing Proxies" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Anonymous Proxy" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Cloud-based Services" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Mobile Threats" + }, + { + "__metadata": { + "groupbyCount": 1 + }, + "alarm": true, + "block": true, + "category": "Tor Proxies" + } + ] + }, + "json-profiles": [ + { + "defenseAttributes": { + "maximumArrayLength": 1000, + "maximumStructureDepth": 10, + "maximumTotalLengthOfJSONData": 10000, + "maximumValueLength": 100, + "tolerateJSONParsingWarnings": false + }, + "description": "Default JSON Profile", + "handleJsonValuesAsParameters": true, + "hasValidationFiles": false, + "name": "Default", + "validationFiles": [] + } + ], + "login-enforcement": { + "expirationTimePeriod": "disabled" + }, + "methods": [ + { + "actAsMethod": "POST", + "name": "PATCH" + }, + { + "actAsMethod": "GET", + "name": "DELETE" + }, + { + "actAsMethod": "GET", + "name": "OPTIONS" + }, + { + "actAsMethod": "GET", + "name": "HEAD" + }, + { + "actAsMethod": "POST", + "name": "PUT" + }, + { + "actAsMethod": "GET", + "name": "GET" + }, + { + "actAsMethod": "POST", + "name": "POST" + } + ], + "name": "new_waf_policy", + "parameters": [ + { + "allowEmptyValue": true, + "allowRepeatedParameterName": false, + "attackSignaturesCheck": true, + "checkMaxValueLength": false, + "checkMetachars": true, + "isBase64": false, + "isCookie": false, + "isHeader": false, + "level": "global", + "metacharsOnParameterValueCheck": true, + "name": "*", + "parameterLocation": "any", + "performStaging": false, + "sensitiveParameter": false, + "type": "wildcard", + "valueType": "auto-detect", + "wildcardOrder": 1 + } + ], + "performStaging": true, + "policy-builder": { + "enableFullPolicyInspection": true, + "enableTrustedTrafficSiteChangeTracking": true, + "enableUntrustedTrafficSiteChangeTracking": true, + "inactiveEntityInactivityDurationInDays": 90, + "learnFromResponses": false, + "learnInactiveEntities": false, + "learnOnlyFromNonBotTraffic": true, + "learningMode": "on-demand", + "responseStatusCodes": [ + "2xx", + "3xx", + "1xx" + ], + "trafficTighten": { + "maxModificationSuggestionScore": 50, + "minDaysBetweenSamples": 1, + "totalRequests": 15000 + }, + "trustAllIps": false, + "trustedTrafficLoosen": { + "differentSources": 1, + "maxDaysBetweenSamples": 7, + "minHoursBetweenSamples": 0 + }, + "trustedTrafficSiteChangeTracking": { + "differentSources": 1, + "maxDaysBetweenSamples": 7, + "minMinutesBetweenSamples": 0 + }, + "untrustedTrafficLoosen": { + "differentSources": 20, + "maxDaysBetweenSamples": 7, + "minHoursBetweenSamples": 1 + }, + "untrustedTrafficSiteChangeTracking": { + "differentSources": 10, + "maxDaysBetweenSamples": 7, + "minMinutesBetweenSamples": 20 + } + }, + "policy-builder-central-configuration": { + "buildingMode": "local", + "eventCorrelationMode": "local" + }, + "policy-builder-cookie": { + "collapseCookiesIntoOneEntity": false, + "enforceUnmodifiedCookies": false, + "learnExplicitCookies": "never", + "maximumCookies": 100 + }, + "policy-builder-filetype": { + "learnExplicitFiletypes": "never", + "maximumFileTypes": 100 + }, + "policy-builder-header": { + "maximumHosts": 10000, + "validHostNames": false + }, + "policy-builder-parameter": { + "classifyParameters": false, + "collapseParametersIntoOneEntity": false, + "dynamicParameters": { + "allHiddenFields": false, + "formParameters": false, + "linkParameters": false, + "uniqueValueSets": 10 + }, + "learnExplicitParameters": "never", + "maximumParameters": 10000, + "parameterLearningLevel": "global", + "parametersIntegerValue": false + }, + "policy-builder-redirection-protection": { + "learnExplicitRedirectionDomains": "never", + "maximumRedirectionDomains": 100 + }, + "policy-builder-server-technologies": { + "enableServerTechnologiesDetection": false + }, + "policy-builder-sessions-and-logins": { + "learnLoginPage": false + }, + "policy-builder-url": { + "classifyUrls": false, + "classifyWebsocketUrls": false, + "collapseUrlsIntoOneEntity": false, + "learnExplicitUrls": "never", + "learnExplicitWebsocketUrls": "never", + "learnMethodsOnUrls": false, + "maximumUrls": 100, + "maximumWebsocketUrls": 100, + "wildcardUrlFiletypes": [ + "swf", + "wav", + "bmp", + "gif", + "pcx", + "pdf", + "png", + "jpeg", + "ico", + "jpg" + ] + }, + "protocolIndependent": true, + "redirection-protection": { + "redirectionProtectionEnabled": false + }, + "response-pages": [ + { + "responseActionType": "soap-fault", + "responsePageType": "xml" + }, + { + "responseActionType": "default", + "responsePageType": "graphql" + }, + { + "responseActionType": "default", + "responsePageType": "default" + }, + { + "responseActionType": "default", + "responsePageType": "captcha" + }, + { + "ajaxActionType": "alert-popup", + "ajaxPopupMessage": "Login Failed. Username or password is incorrect. Please try to log in again.", + "responsePageType": "failed-login-honeypot-ajax" + }, + { + "ajaxActionType": "alert-popup", + "ajaxPopupMessage": "The requested URL was rejected. Please consult with your administrator. Your support ID is: <%TS.request.ID()%>", + "responsePageType": "ajax-login" + }, + { + "responseActionType": "default", + "responsePageType": "hijack" + }, + { + "responseActionType": "default", + "responsePageType": "captcha-fail" + }, + { + "grpcStatusCode": "UNKNOWN", + "grpcStatusMessage": "The request was rejected. Please consult with your administrator. Your support ID is: <%TS.request.ID()%>", + "responsePageType": "grpc" + }, + { + "responseActionType": "default", + "responsePageType": "persistent-flow" + }, + { + "ajaxActionType": "alert-popup", + "ajaxEnabled": false, + "ajaxPopupMessage": "The requested URL was rejected. Please consult with your administrator. Your support ID is: <%TS.request.ID()%>", + "responsePageType": "ajax" + }, + { + "responseActionType": "default", + "responsePageType": "failed-login-honeypot" + }, + { + "responseActionType": "default", + "responsePageType": "mobile" + } + ], + "sensitive-parameters": [ + { + "name": "password" + } + ], + "signature-sets": [ + { + "name": "High Accuracy Signatures", + "alarm": true, + "block": false, + "learn": true + }, + { + "name": "All Signatures", + "alarm": true, + "block": false, + "learn": true + } + ], + "signature-settings": { + "attackSignatureFalsePositiveMode": "disabled", + "minimumAccuracyForAutoAddedSignatures": "high", + "placeSignaturesInStaging": false, + "signatureStaging": false, + "stagingCertificationDatetime": "" + }, + "softwareVersion": "17.0.0", + "ssrf-hosts": [ + { + "action": "disallow", + "host": "*.burpcollaborator.net" + }, + { + "action": "disallow", + "host": "metadata.google.internal" + }, + { + "action": "disallow", + "host": "localhost" + }, + { + "action": "disallow", + "host": "rancher-metadata" + }, + { + "action": "disallow", + "host": "127.0.0.0/24" + }, + { + "action": "disallow", + "host": "192.0.2.0/24" + }, + { + "action": "disallow", + "host": "198.51.100.0/24" + }, + { + "action": "disallow", + "host": "203.0.113.0/24" + }, + { + "action": "disallow", + "host": "169.254.0.0/16" + }, + { + "action": "disallow", + "host": "192.168.0.0/16" + }, + { + "action": "disallow", + "host": "172.16.0.0/12" + }, + { + "action": "disallow", + "host": "fe80::/10" + }, + { + "action": "disallow", + "host": "10.0.0.0/8" + }, + { + "action": "disallow", + "host": "127.0.0.0/8" + }, + { + "action": "disallow", + "host": "0.0.0.0" + }, + { + "action": "disallow", + "host": "0:0:0:0:0:ffff:7f00:1" + }, + { + "action": "disallow", + "host": "100.100.100.200" + }, + { + "action": "disallow", + "host": "168.63.129.16" + }, + { + "action": "disallow", + "host": "169.254.169.254" + }, + { + "action": "disallow", + "host": "192.0.0.192" + }, + { + "action": "disallow", + "host": "::" + }, + { + "action": "disallow", + "host": "::1" + }, + { + "action": "resolve", + "host": "*" + } + ], + "template": { + "name": "" + }, + "threat-campaign-settings": { + "threatCampaignEnforcementReadinessPeriod": 1, + "threatCampaignStaging": false + }, + "type": "security", + "urls": [ + { + "attackSignaturesCheck": true, + "clickjackingProtection": false, + "description": "", + "disallowFileUploadOfExecutables": true, + "html5CrossOriginRequestsEnforcement": { + "enforcementMode": "enforce" + }, + "isAllowed": true, + "mandatoryBody": false, + "metacharsOnUrlCheck": true, + "method": "*", + "methodsOverrideOnUrlCheck": false, + "name": "*", + "performStaging": false, + "protocol": "http", + "type": "wildcard", + "urlContentProfiles": [ + { + "headerName": "*", + "headerOrder": "default", + "headerValue": "*", + "type": "apply-value-and-content-signatures" + }, + { + "headerName": "Content-Type", + "headerOrder": "1", + "headerValue": "*form*", + "type": "form-data" + }, + { + "contentProfile": { + "name": "Default" + }, + "headerName": "Content-Type", + "headerOrder": "2", + "headerValue": "*json*", + "type": "json" + }, + { + "contentProfile": { + "name": "Default" + }, + "headerName": "Content-Type", + "headerOrder": "3", + "headerValue": "*xml*", + "type": "xml" + } + ], + "wildcardIncludesSlash": true, + "wildcardOrder": 2 + } + ], + "xml-profiles": [ + { + "attachmentsInSoapMessages": false, + "attackSignaturesCheck": true, + "defenseAttributes": { + "allowCDATA": false, + "allowDTDs": false, + "allowExternalReferences": false, + "allowProcessingInstructions": true, + "maximumAttributeValueLength": 1024, + "maximumAttributesPerElement": 16, + "maximumChildrenPerElement": 1024, + "maximumDocumentDepth": 32, + "maximumDocumentSize": 1024000, + "maximumElements": 65536, + "maximumNSDeclarations": 64, + "maximumNameLength": 256, + "maximumNamespaceLength": 256, + "tolerateCloseTagShorthand": false, + "tolerateLeadingWhiteSpace": false, + "tolerateNumericNames": false + }, + "description": "Default XML Profile", + "enableWss": false, + "followSchemaLinks": false, + "inspectSoapAttachments": false, + "metacharAttributeCheck": false, + "metacharElementCheck": true, + "name": "Default", + "useXmlResponsePage": false, + "validationFiles": [], + "validationSoapActionHeader": false + } + ] + } + }, + "enforcement_mode": "blocking", + "id": "1a4453fe-b37a-4212-a813-a3d2f789dad1", + "last_modified": "2024-07-17T11:59:25.304421Z", + "name": "new_waf_policy" +} \ No newline at end of file diff --git a/internal/provider/global_resiliency_resource.go b/internal/provider/global_resiliency_resource.go index 73545e8..066d5dc 100644 --- a/internal/provider/global_resiliency_resource.go +++ b/internal/provider/global_resiliency_resource.go @@ -157,7 +157,7 @@ func (r *NextGlobalResiliencyResource) Create(ctx context.Context, req resource. var resCfg *NextGlobalResiliencyResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[CREATE] NextGlobalResiliencyResource:%+v\n", resCfg.Name.ValueString())) @@ -168,7 +168,7 @@ func (r *NextGlobalResiliencyResource) Create(ctx context.Context, req resource. tflog.Info(ctx, fmt.Sprintf("[CREATE] :%+v\n", reqDraft)) id, err := r.client.PostGlobalResiliencyGroup("POST", reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Global Resiliency Error", fmt.Sprintf("Failed to Create Global Resiliency Group, got error: %s", err)) return } @@ -183,13 +183,13 @@ func (r *NextGlobalResiliencyResource) Create(ctx context.Context, req resource. func (r *NextGlobalResiliencyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextGlobalResiliencyResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("Reading Global Resiliency Group : %s", id)) grData, err := r.client.GetGlobalResiliencyGroupDetails(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Error", fmt.Sprintf("Failed to Read Global Resiliency Group, got error: %s", err)) return } @@ -222,7 +222,7 @@ func (r *NextGlobalResiliencyResource) Update(ctx context.Context, req resource. var resCfg *NextGlobalResiliencyResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[UPDATE] Updating Global Resiliency Group: %s", resCfg.Name.ValueString())) @@ -234,7 +234,7 @@ func (r *NextGlobalResiliencyResource) Update(ctx context.Context, req resource. tflog.Info(ctx, fmt.Sprintf("[UPDATE] :%+v\n", reqDraft)) id, err := r.client.PostGlobalResiliencyGroup("PUT", reqDraft) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Update Global Resiliency Group, got error: %s", err)) return } @@ -246,7 +246,7 @@ func (r *NextGlobalResiliencyResource) Update(ctx context.Context, req resource. func (r *NextGlobalResiliencyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextGlobalResiliencyResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -255,7 +255,7 @@ func (r *NextGlobalResiliencyResource) Delete(ctx context.Context, req resource. tflog.Info(ctx, fmt.Sprintf("Deleting Global Resiliency Group : %s", id)) err := r.client.DeleteGlobalResiliencyGroup(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete Global Resiliency Group, got error: %s", err)) return } @@ -263,7 +263,7 @@ func (r *NextGlobalResiliencyResource) Delete(ctx context.Context, req resource. stateCfg.Id = types.StringValue("") } -func (r *NextGlobalResiliencyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *NextGlobalResiliencyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // coverage-ignore // resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } diff --git a/internal/provider/import_certificate_resource.go b/internal/provider/import_certificate_resource.go index 3232dba..7fe8ad2 100644 --- a/internal/provider/import_certificate_resource.go +++ b/internal/provider/import_certificate_resource.go @@ -91,7 +91,7 @@ func (r *NextCMImportCertificateResource) Configure(ctx context.Context, req res func (r *NextCMImportCertificateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var resCfg *NextCMImportCertificateResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[CREATE] NextCMImportCertificateResource:%+v\n", resCfg.Name.ValueString())) @@ -101,7 +101,7 @@ func (r *NextCMImportCertificateResource) Create(ctx context.Context, req resour // tflog.Info(ctx, fmt.Sprintf("[CREATE] :%+v\n", reqDraft)) draftID, err := r.client.PostCertificateCreate(reqDraft, "IMPORT") - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Import Certificate, got error: %s", err)) return } @@ -114,20 +114,20 @@ func (r *NextCMImportCertificateResource) Create(ctx context.Context, req resour func (r *NextCMImportCertificateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextCMImportCertificateResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("Reading Certificate : %s", id)) keycertData, err := r.client.GetNextCMImportCertificateKeyData(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Certificate, Key Data, got error: %s", err)) return } tflog.Info(ctx, fmt.Sprintf("Certificate/Key Data : %+v", keycertData)) certData, err := r.client.GetNextCMCertificate(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Certificate, got error: %s", err)) return } @@ -147,7 +147,7 @@ func (r *NextCMImportCertificateResource) Update(ctx context.Context, req resour var resCfg *NextCMImportCertificateResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, fmt.Sprintf("[UPDATE]Posting Certificate: %s", resCfg.Name.ValueString())) @@ -159,7 +159,7 @@ func (r *NextCMImportCertificateResource) Update(ctx context.Context, req resour tflog.Info(ctx, fmt.Sprintf("[UPDATE] :%+v\n", reqDraft)) draftID, err := r.client.PostCertificateCreate(reqDraft, "UPDATEIMPORT") - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Update Certificate, got error: %s", err)) return } @@ -170,7 +170,7 @@ func (r *NextCMImportCertificateResource) Update(ctx context.Context, req resour func (r *NextCMImportCertificateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextCMImportCertificateResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -179,7 +179,7 @@ func (r *NextCMImportCertificateResource) Delete(ctx context.Context, req resour tflog.Info(ctx, fmt.Sprintf("Deleting Certificate : %s", id)) err := r.client.DeleteNextCMCertificate(id) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete Certificate, got error: %s", err)) return } diff --git a/internal/provider/next_deploy_f5os_resource.go b/internal/provider/next_deploy_f5os_resource.go index bf2ef40..34f37db 100644 --- a/internal/provider/next_deploy_f5os_resource.go +++ b/internal/provider/next_deploy_f5os_resource.go @@ -130,6 +130,7 @@ func (r *NextDeployF5osResource) Schema(ctx context.Context, req resource.Schema Optional: true, Computed: true, ElementType: types.Int64Type, + // Default: listdefault.StaticValue([]int64{1}), }, "cpu_cores": schema.Int64Attribute{ Optional: true, @@ -177,25 +178,27 @@ func (r *NextDeployF5osResource) Configure(ctx context.Context, req resource.Con func (r *NextDeployF5osResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var resCfg *NextDeployF5osResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } var providerModel F5OSProviderModel diag := resCfg.F5OSProvider.As(ctx, &providerModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore return } var instanceModel F5OSInstanceModel diag = resCfg.F5OSInstance.As(ctx, &instanceModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore return } providerID, err := r.client.GetDeviceProviderIDByHostname(providerModel.ProviderName.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get provider ID:, got error: %s", err)) return } + tflog.Info(ctx, fmt.Sprintf("[CREATE] providerID:%+v\n", providerID)) resCfg.ProviderId = types.StringValue(providerID.(string)) + var providerConfig *bigipnextsdk.CMReqDeviceInstance if providerModel.ProviderType.ValueString() == "velos" { providerConfig = f5osVelosConfig(ctx, resCfg) @@ -205,26 +208,45 @@ func (r *NextDeployF5osResource) Create(ctx context.Context, req resource.Create } tflog.Info(ctx, fmt.Sprintf("[CREATE] Deploy Next Instance:%+v\n", providerConfig.Parameters.Hostname)) respData, err := r.client.PostDeviceInstance(providerConfig, int(resCfg.Timeout.ValueInt64())) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Deploy Instance, got error: %s", err)) return } tflog.Info(ctx, fmt.Sprintf("[CREATE] respData ID:%+v\n", string(respData))) resCfg.Id = types.StringValue(providerConfig.Parameters.Hostname) + + elements := []int64{1} + listValue, diags := types.ListValueFrom(ctx, types.Int64Type, elements) + if diags.HasError() { // coverage-ignore + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to set vlan_ids, got error: %s", diags)) + return + } + + f5osInstanceModel := &F5OSInstanceModel{} + f5osInstanceModel.SlotIDs = listValue + resCfg.F5OSInstance.As(ctx, f5osInstanceModel, basetypes.ObjectAsOptions{}) + + tflog.Info(ctx, fmt.Sprintf("[CREATE] listValue :%+v\n", listValue)) + + // resCfg.F5OSInstance.As(ctx, path.Root("slot_ids"), listValue) + // if diags.HasError() { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to set slot_ids, got error: %s", diags)) + // return + // } resp.Diagnostics.Append(resp.State.Set(ctx, resCfg)...) } func (r *NextDeployF5osResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextDeployF5osResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("Reading Device info for : %+v", id)) deviceDetails, err := r.client.GetDeviceIdByHostname(stateCfg.Id.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Device Info, got error: %s", err)) return } @@ -236,7 +258,7 @@ func (r *NextDeployF5osResource) Update(ctx context.Context, req resource.Update var resCfg *NextDeployF5osResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } tflog.Info(ctx, "[UPDATE] Updating Next Instance deployment is not supported") @@ -247,7 +269,7 @@ func (r *NextDeployF5osResource) Update(ctx context.Context, req resource.Update func (r *NextDeployF5osResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextDeployF5osResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -255,21 +277,21 @@ func (r *NextDeployF5osResource) Delete(ctx context.Context, req resource.Delete tflog.Info(ctx, fmt.Sprintf("[DELETE] Deleting Instance from CM : %s", id)) deviceDetails, err := r.client.GetDeviceIdByHostname(stateCfg.Id.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Device Info, got error: %s", err)) return } tflog.Info(ctx, fmt.Sprintf("Device Info : %+v", *deviceDetails)) err = r.client.DeleteDevice(*deviceDetails) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete Instance, got error: %s", err)) return } stateCfg.Id = types.StringValue("") } -func (r *NextDeployF5osResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *NextDeployF5osResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // coverage-ignore resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } diff --git a/internal/provider/next_deploy_f5os_resource_test.go b/internal/provider/next_deploy_f5os_resource_test.go index 408530b..f8b6314 100644 --- a/internal/provider/next_deploy_f5os_resource_test.go +++ b/internal/provider/next_deploy_f5os_resource_test.go @@ -1,11 +1,153 @@ package provider import ( + "fmt" + "net/http" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +func TestUnitNextDeployF5OSResourceTC1(t *testing.T) { + testAccPreUnitCheck(t) + // count := 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/providers", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `[{"instances":[],"provider_host":"10.144.10.146:443","provider_id":"171d5623-e25a-45e5-8e2a-043fc952cbf1","provider_name":"myrseries","provider_type":"RSERIES","provider_username":"admin","updated":"2024-07-16T17:16:42.638833Z"}]`) + }) + mux.HandleFunc("/api/device/v1/instances", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e"}},"path":"/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e"}`) + }) + + mux.HandleFunc("/api/device/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e"}},"completed":"2024-07-16T17:25:01.919956Z","created":"2024-07-16T17:16:46.036781Z","failure_reason":"","id":"7597ff87-d26d-4154-b03d-9e7999d24f0e","name":"instance creation","payload":{"discovery":{"port":5443,"address":"10.144.10.182","device_user":"admin","device_password":"*****","management_user":"admin-cm","management_password":"*****"},"onboarding":{"mode":"STANDALONE","nodes":[{"password":"*****","username":"admin","managementAddress":"10.144.10.182"}],"platformType":"RSERIES"},"instantiation":{"Request":{"F5osRequest":{"provider_id":"171d5623-e25a-45e5-8e2a-043fc952cbf1","provider_type":"rseries","next_instances":[{"nodes":[1],"vlans":[444,555],"mgmt_ip":"10.144.10.182","timeout":600,"hostname":"demovm01-ravi-r10800","cpu_cores":4,"disk_size":30,"mgmt_prefix":24,"mgmt_gateway":"10.144.10.254","admin_password":"*****","tenant_image_name":"BIG-IP-Next-20.2.1-2.430.2+0.0.48","tenant_deployment_file":"BIG-IP-Next-20.2.1-2.430.2+0.0.48.yaml"}]},"VsphereRequest":null},"BaseTask":{"id":"","payload":null,"provider_id":"171d5623-e25a-45e5-8e2a-043fc952cbf1","provider_type":"rseries"},"VsphereRequest":null}},"stage":"Discovery","state":"discoveryDone","status":"completed","task_type":"instance_creation","updated":"2024-07-16T17:25:01.919956Z"}`) + }) + + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + }) + // mux.HandleFunc("/api/device/v1/inventory?filter=hostname+eq+'demovm01-ravi-r10800'", func(w http.ResponseWriter, r *http.Request) { + // w.WriteHeader(http.StatusOK) + // _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + // }) + mux.HandleFunc("/api/v1/spaces/default/instances/6cdf38ed-a258-4d92-a64d-972238b27400", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if r.Method == http.MethodDelete { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/instances/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26"}},"path":"/api/v1/spaces/default/instances/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26"}`) + } else { + _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + } + }) + // mux.HandleFunc("/api/v1/spaces/default/instances/6cdf38ed-a258-4d92-a64d-972238b27400", func(w http.ResponseWriter, r *http.Request) { + // + // }) + mux.HandleFunc("/api/device/v1/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/v1/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26"}},"address":"10.144.10.182","completed":"2024-07-16T17:35:42.811049Z","created":"2024-07-16T17:32:36.411802Z","device_id":"6cdf38ed-a258-4d92-a64d-972238b27400","failure_reason":"","id":"837120e1-7420-4e86-afc5-213dd2b07f26","state":"instanceRemovalDone","status":"completed"}`) + }) + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextDeployF5OSResourceTC1Config, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + // ImportState testing + { + ResourceName: "bigipnext_cm_deploy_f5os.rseries01", + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +func TestUnitNextDeployF5OSResourceTC2(t *testing.T) { + testAccPreUnitCheck(t) + // count := 0 + mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{ + "token": "eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.eyJhbGciOiJIUzM4NCIsImtpZCI6IjJiMGE4MjEwLWJhYmQtNDRhZi04MmMyLTI2YWE4Yjk3OWYwMCIsInR5cCI6IkpXVCJ9.AbY1hUw8wHO8Vt1qxRd5xQj_21EQ1iaH6q9Z2XgRwQl98M7aCpyjiF2J16S4HrZ-", + "tokenType": "Bearer", + "expiresIn": 3600, + "refreshToken": "ODA0MmQzZTctZTk1Mi00OTk1LWJmMjUtZWZmMjc1NDE3YzliOt4bKlRr6g7RdTtnBKhm2vzkgJeWqfvow68gyxTipleCq4AjR4nxZDBYKQaWyCWGeA", + "refreshExpiresIn": 1209600 + }`) + }) + mux.HandleFunc("/api/v1/spaces/default/providers", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `[{"instances":[],"provider_host":"10.144.10.146:443","provider_id":"171d5623-e25a-45e5-8e2a-043fc952cbf1","provider_name":"myvelos","provider_type":"VELOS","provider_username":"admin","updated":"2024-07-16T17:16:42.638833Z"}]`) + }) + mux.HandleFunc("/api/device/v1/instances", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e"}},"path":"/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e"}`) + }) + + mux.HandleFunc("/api/device/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/v1/instances/tasks/7597ff87-d26d-4154-b03d-9e7999d24f0e"}},"completed":"2024-07-16T17:25:01.919956Z","created":"2024-07-16T17:16:46.036781Z","failure_reason":"","id":"7597ff87-d26d-4154-b03d-9e7999d24f0e","name":"instance creation","payload":{"discovery":{"port":5443,"address":"10.144.10.182","device_user":"admin","device_password":"*****","management_user":"admin-cm","management_password":"*****"},"onboarding":{"mode":"STANDALONE","nodes":[{"password":"*****","username":"admin","managementAddress":"10.144.10.182"}],"platformType":"RSERIES"},"instantiation":{"Request":{"F5osRequest":{"provider_id":"171d5623-e25a-45e5-8e2a-043fc952cbf1","provider_type":"rseries","next_instances":[{"nodes":[1],"vlans":[444,555],"mgmt_ip":"10.144.10.182","timeout":600,"hostname":"demovm01-ravi-r10800","cpu_cores":4,"disk_size":30,"mgmt_prefix":24,"mgmt_gateway":"10.144.10.254","admin_password":"*****","tenant_image_name":"BIG-IP-Next-20.2.1-2.430.2+0.0.48","tenant_deployment_file":"BIG-IP-Next-20.2.1-2.430.2+0.0.48.yaml"}]},"VsphereRequest":null},"BaseTask":{"id":"","payload":null,"provider_id":"171d5623-e25a-45e5-8e2a-043fc952cbf1","provider_type":"rseries"},"VsphereRequest":null}},"stage":"Discovery","state":"discoveryDone","status":"completed","task_type":"instance_creation","updated":"2024-07-16T17:25:01.919956Z"}`) + }) + + mux.HandleFunc("/api/device/v1/inventory", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + }) + // mux.HandleFunc("/api/device/v1/inventory?filter=hostname+eq+'demovm01-ravi-r10800'", func(w http.ResponseWriter, r *http.Request) { + // w.WriteHeader(http.StatusOK) + // _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + // }) + mux.HandleFunc("/api/v1/spaces/default/instances/6cdf38ed-a258-4d92-a64d-972238b27400", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + if r.Method == http.MethodDelete { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/instances/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26"}},"path":"/api/v1/spaces/default/instances/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26"}`) + } else { + _, _ = fmt.Fprintf(w, `{"_embedded":{"devices":[{"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800/6cdf38ed-a258-4d92-a64d-972238b27400'"}},"address":"10.144.10.182","certificate_validated":"2024-07-16T17:24:12.848618Z","certificate_validity":false,"hostname":"demovm01-ravi-r10800","id":"6cdf38ed-a258-4d92-a64d-972238b27400","mode":"STANDALONE","platform_name":"R10K","platform_type":"APPLIANCE","port":5443,"short_id":"bcpED8hJ","version":"20.2.1-2.430.2+0.0.48"}]},"_links":{"self":{"href":"/v1/inventory?filter=hostname+eq+demovm01-ravi-r10800"}},"count":1,"total":1}`) + } + }) + // mux.HandleFunc("/api/v1/spaces/default/instances/6cdf38ed-a258-4d92-a64d-972238b27400", func(w http.ResponseWriter, r *http.Request) { + // + // }) + mux.HandleFunc("/api/device/v1/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/v1/deletion-tasks/837120e1-7420-4e86-afc5-213dd2b07f26"}},"address":"10.144.10.182","completed":"2024-07-16T17:35:42.811049Z","created":"2024-07-16T17:32:36.411802Z","device_id":"6cdf38ed-a258-4d92-a64d-972238b27400","failure_reason":"","id":"837120e1-7420-4e86-afc5-213dd2b07f26","state":"instanceRemovalDone","status":"completed"}`) + }) + defer teardown() + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccNextDeployF5OSResourceTC2Config, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + { + Config: testAccNextDeployF5OSResourceTC2UpdateConfig, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + func TestAccNextDeployF5OSResourceTC1(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -48,6 +190,48 @@ resource "bigipnext_cm_deploy_f5os" "rseries01" { management_user = "admin-cm" management_password = "F5Twist@123" vlan_ids = [27, 28, 29] + slot_ids = [1] + tenant_deployment_file = "BIG-IP-Next-20.1.0-2.279.0+0.0.75.yaml" + tenant_image_name = "BIG-IP-Next-20.1.0-2.279.0+0.0.75" + } +} +` +const testAccNextDeployF5OSResourceTC2Config = ` +resource "bigipnext_cm_deploy_f5os" "velos01" { + f5os_provider = { + provider_name = "myvelos" + provider_type = "velos" + } + instance = { + instance_hostname = "rseriesravitest04" + management_address = "10.144.140.81" + management_prefix = 24 + management_gateway = "10.144.140.254" + management_user = "admin-cm" + management_password = "F5Twist@123" + vlan_ids = [27, 28, 29] + slot_ids = [2] + tenant_deployment_file = "BIG-IP-Next-20.1.0-2.279.0+0.0.75.yaml" + tenant_image_name = "BIG-IP-Next-20.1.0-2.279.0+0.0.75" + } +} +` + +const testAccNextDeployF5OSResourceTC2UpdateConfig = ` +resource "bigipnext_cm_deploy_f5os" "velos01" { + f5os_provider = { + provider_name = "myvelos" + provider_type = "velos" + } + instance = { + instance_hostname = "rseriesravitest04" + management_address = "10.144.140.82" + management_prefix = 24 + management_gateway = "10.144.140.254" + management_user = "admin-cm" + management_password = "F5Twist@123" + vlan_ids = [27, 28, 29] + slot_ids = [2] tenant_deployment_file = "BIG-IP-Next-20.1.0-2.279.0+0.0.75.yaml" tenant_image_name = "BIG-IP-Next-20.1.0-2.279.0+0.0.75" } diff --git a/internal/provider/next_deploy_vmware_resource.go b/internal/provider/next_deploy_vmware_resource.go index 736147e..3cc71ce 100644 --- a/internal/provider/next_deploy_vmware_resource.go +++ b/internal/provider/next_deploy_vmware_resource.go @@ -255,21 +255,21 @@ func (r *NextDeployVmwareResource) Configure(ctx context.Context, req resource.C func (r *NextDeployVmwareResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var resCfg *NextDeployVmwareResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } var providerModel VsphereProviderModel diag := resCfg.VsphereProvider.As(ctx, &providerModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore return } var instanceModel InstanceModel diag = resCfg.Instance.As(ctx, &instanceModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore return } providerID, err := r.client.GetDeviceProviderIDByHostname(providerModel.ProviderName.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get provider ID:, got error: %s", err)) return } @@ -277,13 +277,13 @@ func (r *NextDeployVmwareResource) Create(ctx context.Context, req resource.Crea providerConfig := instanceConfig(ctx, resCfg) tflog.Info(ctx, fmt.Sprintf("[CREATE] Deploy Next Instance:%+v\n", providerConfig.Parameters.Hostname)) respData, err := r.client.PostDeviceInstance(providerConfig, int(resCfg.Timeout.ValueInt64())) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Deploy Instance, got error: %s", err)) return } tflog.Info(ctx, fmt.Sprintf("[CREATE] respData ID:%+v\n", respData)) deviceDetails, err := r.client.GetDeviceIdByHostname(providerConfig.Parameters.Hostname) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Device Info, got error: %s", err)) return } @@ -295,14 +295,14 @@ func (r *NextDeployVmwareResource) Create(ctx context.Context, req resource.Crea func (r *NextDeployVmwareResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var stateCfg *NextDeployVmwareResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } id := stateCfg.Id.ValueString() tflog.Info(ctx, fmt.Sprintf("Reading Device info for : %+v", id)) deviceDetails, err := r.client.GetDeviceIdByHostname(stateCfg.Id.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Device Info, got error: %s", err)) return } @@ -314,7 +314,7 @@ func (r *NextDeployVmwareResource) Update(ctx context.Context, req resource.Upda var resCfg *NextDeployVmwareResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &resCfg)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } providerConfig := instanceConfig(ctx, resCfg) @@ -325,7 +325,7 @@ func (r *NextDeployVmwareResource) Update(ctx context.Context, req resource.Upda func (r *NextDeployVmwareResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var stateCfg *NextDeployVmwareResourceModel - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } resp.Diagnostics.Append(req.State.Get(ctx, &stateCfg)...) @@ -333,14 +333,14 @@ func (r *NextDeployVmwareResource) Delete(ctx context.Context, req resource.Dele tflog.Info(ctx, fmt.Sprintf("[DELETE] Deleting Instance from CM : %s", id)) deviceDetails, err := r.client.GetDeviceIdByHostname(stateCfg.Id.ValueString()) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to Read Device Info, got error: %s", err)) return } tflog.Info(ctx, fmt.Sprintf("Device Info : %+v", *deviceDetails)) err = r.client.DeleteDevice(*deviceDetails) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to Delete Instance, got error:%s", err)) return } @@ -356,12 +356,12 @@ func instanceConfig(ctx context.Context, data *NextDeployVmwareResourceModel) *b deployConfig.TemplateName = "default-standalone-ve" var providerModel VsphereProviderModel diag := data.VsphereProvider.As(ctx, &providerModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore tflog.Error(ctx, fmt.Sprintf("VsphereProviderModel diag Error: %+v", diag.Errors())) } var instanceModel InstanceModel diag = data.Instance.As(ctx, &instanceModel, basetypes.ObjectAsOptions{}) - if diag.HasError() { + if diag.HasError() { // coverage-ignore tflog.Error(ctx, fmt.Sprintf("InstanceModel diag Error: %+v", diag.Errors())) } var cmReqDeviceInstance bigipnextsdk.CMReqDeviceInstance @@ -409,7 +409,7 @@ func instanceConfig(ctx context.Context, data *NextDeployVmwareResourceModel) *b l1Networks.Vlans[index].Tag = int(vlan.VlanTag.ValueInt64()) elements := make([]types.String, 0, len(vlan.SelfIps.Elements())) diags := vlan.SelfIps.ElementsAs(ctx, &elements, false) - if diags.HasError() { + if diags.HasError() { // coverage-ignore tflog.Error(ctx, fmt.Sprintf("SelfIps diag Error: %+v", diags.Errors())) } l1Networks.Vlans[index].SelfIps = make([]bigipnextsdk.CMReqSelfIps, len(elements)) diff --git a/internal/provider/next_deploy_vmware_resource_test.go b/internal/provider/next_deploy_vmware_resource_test.go index 2be9c0b..efcb719 100644 --- a/internal/provider/next_deploy_vmware_resource_test.go +++ b/internal/provider/next_deploy_vmware_resource_test.go @@ -51,7 +51,7 @@ func TestUnitNextDeployVmwareResourceTC1(t *testing.T) { "refreshExpiresIn": 1209600 }`) }) - mux.HandleFunc("/device/v1/providers?filter=name+eq+'myvsphere03'", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/v1/spaces/default/providers?filter=name+eq+'myvsphere03'", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, `{"_links":{"self":{"href":"/api/v1/spaces/default/certificates/create"}},"path":"/v1/certificates/43b7bd5b-5b61-4a64-8fe4-68ef8ed910f2"}`) }) @@ -82,7 +82,7 @@ func TestUnitNextDeployVmwareResourceTC2(t *testing.T) { "refreshExpiresIn": 1209600 }`) }) - mux.HandleFunc("/api/device/v1/providers", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/v1/spaces/default/providers", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, `[{"instances":[{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"}],"provider_host":"mbip-70-vcenter.pdsea.f5net.com","provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b","provider_name":"myvsphere03","provider_type":"VSPHERE","provider_username":"r.chinthalapalli@f5.com","updated":"2023-12-12T11:53:58.614102Z"}]`) }) @@ -117,7 +117,7 @@ func TestUnitNextDeployVmwareResourceTC3(t *testing.T) { "refreshExpiresIn": 1209600 }`) }) - mux.HandleFunc("/api/device/v1/providers", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/v1/spaces/default/providers", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, `[{"instances":[{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"}],"provider_host":"mbip-70-vcenter.pdsea.f5net.com","provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b","provider_name":"myvsphere03","provider_type":"VSPHERE","provider_username":"r.chinthalapalli@f5.com","updated":"2023-12-12T11:53:58.614102Z"}]`) }) @@ -161,7 +161,7 @@ func TestUnitNextDeployVmwareResourceTC3(t *testing.T) { // "refreshExpiresIn": 1209600 // }`) // }) -// mux.HandleFunc("/api/device/v1/providers", func(w http.ResponseWriter, r *http.Request) { +// mux.HandleFunc("/api/v1/spaces/default/providers", func(w http.ResponseWriter, r *http.Request) { // w.WriteHeader(http.StatusOK) // _, _ = fmt.Fprintf(w, `[{"instances":[{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"}],"provider_host":"mbip-70-vcenter.pdsea.f5net.com","provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b","provider_name":"myvsphere03","provider_type":"VSPHERE","provider_username":"r.chinthalapalli@f5.com","updated":"2023-12-12T11:53:58.614102Z"}]`) // }) @@ -209,7 +209,7 @@ func TestUnitNextDeployVmwareResourceTC5(t *testing.T) { "refreshExpiresIn": 1209600 }`) }) - mux.HandleFunc("/api/device/v1/providers", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/api/v1/spaces/default/providers", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, `[{"instances":[{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"},{"provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b"}],"provider_host":"mbip-70-vcenter.pdsea.f5net.com","provider_id":"9945d9cd-9f13-438f-8b97-f0cb1745d32b","provider_name":"myvsphere03","provider_type":"VSPHERE","provider_username":"r.chinthalapalli@f5.com","updated":"2023-12-12T11:53:58.614102Z"}]`) }) @@ -274,6 +274,13 @@ resource "bigipnext_cm_deploy_vmware" "vmware" { internal_network_name = "LocalTestVLAN-114" ha_data_plane_network_name = "LocalTestVLAN-116" } + l1_networks = [{ + name = "demonetwork1" + vlans = [{ + vlan_tag = 115 + vlan_name = "vlan-115" + self_ips=["10.101.10.10/24","10.101.10.11/24"]}] + }] ntp_servers = ["0.us.pool.ntp.org"] dns_servers = ["8.8.8.8"] timeout = 1200 diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 045e3b2..4b989cf 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -80,7 +80,7 @@ func (p *BigipNextCMProvider) Configure(ctx context.Context, req provider.Config var config BigipNextCMProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { + if resp.Diagnostics.HasError() { // coverage-ignore return } @@ -90,15 +90,15 @@ func (p *BigipNextCMProvider) Configure(ctx context.Context, req provider.Config username := os.Getenv("BIGIPNEXT_USERNAME") password := os.Getenv("BIGIPNEXT_PASSWORD") - if !config.Host.IsNull() { + if !config.Host.IsNull() { // coverage-ignore host = config.Host.ValueString() } - if !config.Username.IsNull() { + if !config.Username.IsNull() { // coverage-ignore username = config.Username.ValueString() } - if !config.Password.IsNull() { + if !config.Password.IsNull() { // coverage-ignore password = config.Password.ValueString() } ctx = tflog.SetField(ctx, "bigipnext_host", host) @@ -113,7 +113,7 @@ func (p *BigipNextCMProvider) Configure(ctx context.Context, req provider.Config } tflog.Debug(ctx, fmt.Sprintf("bigipnextCmConfig client:%+v", bigipnextCmConfig)) client, err := bigipnextsdk.CmNewSession(bigipnextCmConfig) - if err != nil { + if err != nil { // coverage-ignore resp.Diagnostics.AddError( "Unable to Create bigipnext CM Client", "An unexpected error occurred when creating the bigipnext client. "+ @@ -178,7 +178,9 @@ func (p *BigipNextCMProvider) Configure(ctx context.Context, req provider.Config func (p *BigipNextCMProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ NewNextCMAS3DeployResource, + // NewNextCMFastHttpResource, NewNextCMBackupRestoreResource, + // NewNextCMFastTemplateResource, NewNextCMCertificateResource, NewNextCMImportCertificateResource, NewNextCMDeviceProviderResource, @@ -191,6 +193,9 @@ func (p *BigipNextCMProvider) Resources(ctx context.Context) []func() resource.R NewNextCMWAFPolicyResource, NewNextCMWAFPolicyImportResource, NewNextCMHAClusterResource, + NewCMNextJwtTokenResource, + NewCMNextLicenseActivateResource, + NewCMNextBootstrapResource, } } @@ -219,7 +224,7 @@ func toBigipNextCMProvider(in any) (*bigipnextsdk.BigipNextCM, diag.Diagnostics) p, ok := in.(*bigipnextsdk.BigipNextCM) - if !ok { + if !ok { // coverage-ignore diags.AddError( "Unexpected Provider Instance Type", fmt.Sprintf("While creating the data source or resource, an unexpected provider type (%T) was received. "+ diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 9b2e072..72eade9 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -71,16 +71,16 @@ func teardown() { server.Close() } -// // loadFixtureBytes returns the entire contents of the given file as a byte slice -// func loadFixtureBytes(path string) []byte { -// contents, err := os.ReadFile(path) -// if err != nil { -// panic(err) -// } -// return contents -// } +// loadFixtureBytes returns the entire contents of the given file as a byte slice +func loadFixtureBytes(path string) []byte { + contents, err := os.ReadFile(path) + if err != nil { + panic(err) + } + return contents +} -// // loadFixtureString returns the entire contents of the given file as a string -// func loadFixtureString(path string) string { -// return string(loadFixtureBytes(path)) -// } +// loadFixtureString returns the entire contents of the given file as a string +func loadFixtureString(path string) string { + return string(loadFixtureBytes(path)) +} diff --git a/internal/provider/providerplanmodifier.go b/internal/provider/providerplanmodifier.go index e3cfa02..5e4dbba 100644 --- a/internal/provider/providerplanmodifier.go +++ b/internal/provider/providerplanmodifier.go @@ -1,12 +1,15 @@ +//go:build !test + package provider import ( "context" "encoding/json" "fmt" + "reflect" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/types" - "reflect" ) // Int64DefaultValue is assign default values for Int Type diff --git a/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/bigipnextcm.go b/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/bigipnextcm.go index ae81cfc..15ad847 100644 --- a/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/bigipnextcm.go +++ b/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/bigipnextcm.go @@ -32,14 +32,12 @@ const ( uriCMFileUpload = "/system/v1/files" uriFast = "/mgmt/shared/fast" uriOpenAPI = "/openapi" - uriInventory = "/device/v1/inventory" uriBackups = "/device/v1/backups" uriBackupTasks = "/device/v1/backup-tasks" uriRestoreTasks = "/device/v1/restore-tasks" uriCertificate = "/api/v1/spaces/default/certificates" + uriLicenseToken = "/api/v1/spaces/default/license/tokens" uriCMUpgradeTask = "/upgrade-manager/v1/upgrade-tasks" - uriAS3Root = "/api/v1/spaces/default/appsvcs" - uriDiscoverInstance = "/v1/spaces/default/instances" // uriCertificateUpdate = "/api/certificate/v1/certificates" uriGlobalResiliency = "/api/v1/spaces/default/gslb/gr-groups" uriGetGlobalResiliency = "/v1/spaces/default/gslb/gr-groups" @@ -92,30 +90,6 @@ type CMError struct { Code int } -type DeviceInventoryList struct { - Embedded struct { - Devices []struct { - Links struct { - Self struct { - Href string `json:"href"` - } `json:"self"` - } `json:"_links"` - Address string `json:"address"` - CertificateValidated time.Time `json:"certificate_validated"` - CertificateValidationError string `json:"certificate_validation_error"` - CertificateValidity bool `json:"certificate_validity"` - Hostname string `json:"hostname"` - Id string `json:"id"` - Mode string `json:"mode"` - PlatformType string `json:"platform_type"` - Port int `json:"port"` - Version string `json:"version"` - } `json:"devices"` - } `json:"_embedded"` - Count int `json:"count"` - Total int `json:"total"` -} - // CmNewSession sets up connection to the BIG-IP Next CM system. func CmNewSession(bigipNextCmObj *BigipNextCMReqConfig) (*BigipNextCM, error) { @@ -723,6 +697,111 @@ func (p *BigipNextCM) DeleteNextCMCertificate(id string) error { return nil } +type JWTRequestDraft struct { + NickName string `json:"nickName,omitempty"` + JWT string `json:"jwt,omitempty"` +} + +// https://clouddocs.f5.com/api/v1/spaces/default/license/tokens +func (p *BigipNextCM) PostLicenseToken(config *JWTRequestDraft) ([]byte, error) { + licenseURL := fmt.Sprintf("%s%s", p.Host, uriLicenseToken) + f5osLogger.Info("[PostLicenseToken]", "URI Path", licenseURL) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.doCMRequest("POST", licenseURL, body) + if err != nil { + return nil, err + } + mapResp := make(map[string]interface{}) + err = json.Unmarshal(respData, &mapResp) + if err != nil { + return nil, err + } + var tokenID string + f5osLogger.Info("[PostLicenseToken]", "Token Resp::", hclog.Fmt("%+v", string(respData))) + f5osLogger.Info("[PostLicenseToken]", "NewToken", mapResp["NewToken"]) + if mapResp["NewToken"].(map[string]interface{})["nickName"] == config.NickName { + tokenID = mapResp["NewToken"].(map[string]interface{})["id"].(string) + return []byte(tokenID), nil + } + f5osLogger.Info("[PostLicenseToken]", "tokenID", tokenID) + + return nil, nil +} + +// https://clouddocs.f5.com/api/v1/spaces/default/license/tokens/verify +func (p *BigipNextCM) PostLicenseTokenVerify(config *JWTRequestDraft) ([]byte, error) { + licenseURL := fmt.Sprintf("%s%s%s", p.Host, uriLicenseToken, "/verify") + f5osLogger.Info("[PostLicenseTokenVerify]", "URI Path", licenseURL) + jwtMap := make(map[string]interface{}) + // map[string]interface{} + jwtMap["jwt"] = config.JWT + body, err := json.Marshal(jwtMap) + if err != nil { + return nil, err + } + respData, err := p.doCMRequest("POST", licenseURL, body) + if err != nil { + return nil, err + } + f5osLogger.Info("[PostLicenseTokenVerify]", "Data::", hclog.Fmt("%+v", string(respData))) + return respData, nil +} + +// api/v1/spaces/default/license/tokens +func (p *BigipNextCM) GetLicenseTokens() ([]byte, error) { + // fileUrl := fmt.Sprintf("%s?filter=file_name+eq+'%s'", uriCMFileUpload, fileName) + licenseURL := fmt.Sprintf("%s%s", p.Host, uriLicenseToken) + f5osLogger.Info("[GetLicenseTokens]", "URI Path", licenseURL) + respData, err := p.doCMRequest("GET", licenseURL, nil) + if err != nil { + return nil, err + } + f5osLogger.Info("[GetLicenseTokens]", "Data::", hclog.Fmt("%+v", string(respData))) + return respData, nil +} + +// https://clouddocs.f5.com/api/v1/spaces/default/license/tokens/{token_id} +func (p *BigipNextCM) GetLicenseToken(tokenID string) (interface{}, error) { + licenseURL := fmt.Sprintf("%s%s/%s", p.Host, uriLicenseToken, tokenID) + f5osLogger.Info("[GetLicenseToken]", "URI Path", licenseURL) + respData, err := p.doCMRequest("GET", licenseURL, nil) + if err != nil { + return nil, err + } + // { + // "entitlement": "{\"compliance\":{\"digitalAssetComplianceStatus\":\"\",\"digitalAssetDaysRemainingInState\":0,\"digitalAssetExpiringSoon\":false,\"digitalAssetOutOfComplianceDate\":\"\",\"entitlementCheckStatus\":\"\",\"entitlementExpiryStatus\":\"\",\"telemetryStatus\":\"\",\"usageExceededStatus\":\"\"},\"documentType\":\"BIG-IP Next License\",\"documentVersion\":\"1\",\"digitalAsset\":{\"digitalAssetId\":\"\",\"digitalAssetName\":\"\",\"digitalAssetVersion\":\"\",\"telemetryId\":\"\"},\"entitlementMetadata\":{\"complianceEnforcements\":null,\"complianceStates\":null,\"enforcementBehavior\":\"\",\"enforcementPeriodDays\":0,\"entitlementModel\":\"\",\"expiringSoonNotificationDays\":0,\"entitlementExpiryDate\":\"0001-01-01T00:00:00Z\",\"gracePeriodDays\":0,\"nonContactPeriodHours\":0,\"nonFunctionalPeriodDays\":0,\"orderSubType\":\"\",\"orderType\":\"\"},\"subscriptionMetadata\":{\"programName\":\"big_ip_next_internal\",\"programTypeDescription\":\"big_ip_next_internal\",\"subscriptionId\":\"A-S00019374\",\"subscriptionExpiryDate\":\"2024-12-07T00:00:00.000Z\",\"subscriptionNotifyDays\":\"\"},\"RepositoryCertificateMetadata\":{\"sslCertificate\":\"\",\"privateKey\":\"\"}}", + // "id": "69609dcd-b2d4-480e-bf06-6848556a1e59", + // "nickName": "paid_test_jwt", + // "orderSubType": "internal", + // "orderType": "paid", + // "subscriptionExpiry": "2024-12-07T00:00:00Z" + // } + // create map + var respMap map[string]interface{} + err = json.Unmarshal(respData, &respMap) + if err != nil { + return nil, err + } + f5osLogger.Info("[GetLicenseToken]", "Data::", hclog.Fmt("%+v", respMap)) + return respMap, nil +} + +// https://clouddocs.f5.com/api/v1/spaces/default/license/tokens/{token_id} +// delete request to delete license token +func (p *BigipNextCM) DeleteLicenseToken(tokenID string) error { + licenseURL := fmt.Sprintf("%s%s/%s", p.Host, uriLicenseToken, tokenID) + f5osLogger.Info("[DeleteLicenseToken]", "URI Path", licenseURL) + respData, err := p.doCMRequest("DELETE", licenseURL, nil) + if err != nil { + return err + } + f5osLogger.Info("[DeleteLicenseToken]", "Data::", hclog.Fmt("%+v", string(respData))) + return nil +} + type ImportCertificateRequestDraft struct { Name string `json:"name,omitempty"` KeyPassphrase string `json:"key_passphrase,omitempty"` @@ -1288,155 +1367,6 @@ func (p *BigipNextCM) ProxyFileUpload(proxyID, filePath string) ([]byte, error) return nil, nil } -// /api/v1/spaces/default/appsvcs/documents -func (p *BigipNextCM) PostAS3DraftDocument(config string) (string, error) { - as3DraftURL := fmt.Sprintf("%s%s%s", p.Host, uriAS3Root, "/documents") - f5osLogger.Info("[PostAS3DraftDocument]", "URI Path", as3DraftURL) - f5osLogger.Info("[PostAS3DraftDocument]", "Config", hclog.Fmt("%+v", config)) - respData, err := p.doCMRequest("POST", as3DraftURL, []byte(config)) - if err != nil { - return "", err - } - f5osLogger.Info("[PostAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) - //{"Message":"Application service created successfully","_links":{"self":{"href":"/api/v1/spaces/default/appsvcs/documents/3a220683-6527-4443-8da7-279680c21ac5"}},"id":"3a220683-6527-4443-8da7-279680c21ac5"} - respString := make(map[string]interface{}) - err = json.Unmarshal(respData, &respString) - if err != nil { - return "", err - } - f5osLogger.Info("[PostAS3DraftDocument]", "Document Drart", hclog.Fmt("%+v", respString["id"].(string))) - return respString["id"].(string), nil -} - -// /api/v1/spaces/default/appsvcs/documents/3a220683-6527-4443-8da7-279680c21ac5 -func (p *BigipNextCM) GetAS3DraftDocument(docID string) ([]byte, error) { - as3DraftURL := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) - f5osLogger.Info("[GetAS3DraftDocument]", "URI Path", as3DraftURL) - respData, err := p.doCMRequest("GET", as3DraftURL, nil) - if err != nil { - return []byte(""), err - } - f5osLogger.Info("[GetAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) - return respData, nil -} - -func (p *BigipNextCM) PutAS3DraftDocument(docID, config string) error { - as3DraftURL := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) - f5osLogger.Info("[PutAS3DraftDocument]", "URI Path", as3DraftURL) - f5osLogger.Info("[PutAS3DraftDocument]", "Config", hclog.Fmt("%+v", config)) - respData, err := p.doCMRequest("PUT", as3DraftURL, []byte(config)) - if err != nil { - return err - } - f5osLogger.Info("[PutAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) - return nil -} - -// /api/v1/spaces/default/appsvcs/documents/3a220683-6527-4443-8da7-279680c21ac5 -func (p *BigipNextCM) DeleteAS3DraftDocument(docID string) error { - as3DraftURL := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) - f5osLogger.Info("[DeleteAS3DraftDocument]", "URI Path", as3DraftURL) - respData, err := p.doCMRequest("DELETE", as3DraftURL, nil) - if err != nil { - return err - } - f5osLogger.Info("[DeleteAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) - return nil -} - -// https://clouddocs.f5.com/api/v1/spaces/default/appsvcs/documents/{document-id}/deployments -func (p *BigipNextCM) CMAS3DeployNext(draftID, target string, timeOut int) (string, error) { - as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s", p.Host, uriAS3Root, "/documents", draftID, "deployments") - f5osLogger.Info("[CMAS3DeployNext]", "URI Path", as3DeployUrl) - as3Json := make(map[string]interface{}) - as3Json["target"] = target - as3data, err := json.Marshal(as3Json) - if err != nil { - return "", err - } - f5osLogger.Info("[CMAS3DeployNext]", "Data::", hclog.Fmt("%+v", string(as3data))) - respData, err := p.doCMRequest("POST", as3DeployUrl, as3data) - if err != nil { - return "", err - } - f5osLogger.Info("[CMAS3DeployNext]", "Data::", hclog.Fmt("%+v", string(respData))) - //{ "Message": "Deployment task created successfully", "_links": { "self": { "href": "/declare/1a5a6049-8220-483a-8cbc-275a4b190d35/deployments/2ceb048a-0ee6-4a2d-8952-cd15583bb5e8" } }, "id": "2ceb048a-0ee6-4a2d-8952-cd15583bb5e8" } - respString := make(map[string]interface{}) - err = json.Unmarshal(respData, &respString) - if err != nil { - return "", err - } - f5osLogger.Info("[CMAS3DeployNext]", "Deployment Task", hclog.Fmt("%+v", respString["id"].(string))) - _, err = p.getAS3DeploymentTaskStatus(draftID, respString["id"].(string), timeOut) - if err != nil { - return "", err - } - return respString["id"].(string), nil -} - -// https://clouddocs.f5.com/api/v1/spaces/default/appsvcs/documents/{document-id}/deployments/{deployment-id} -func (p *BigipNextCM) GetAS3DeploymentTaskStatus(docID, deployID string) (interface{}, error) { - as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s/%s", p.Host, uriAS3Root, "/documents", docID, "deployments", deployID) - f5osLogger.Info("[GetAS3DeploymentTaskStatus]", "URI Path", as3DeployUrl) - return p.getAS3DeploymentTaskStatus(docID, deployID, 60) -} - -// https://clouddocs.f5.com/api/v1/spaces/default/appsvcs/documents/{document-id}/deployments/{deployment-id} -func (p *BigipNextCM) getAS3DeploymentTaskStatus(docID, deployID string, timeOut int) (interface{}, error) { - as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s/%s", p.Host, uriAS3Root, "/documents", docID, "deployments", deployID) - f5osLogger.Info("[getAS3DeploymentTaskStatus]", "URI Path", as3DeployUrl) - responseData := make(map[string]interface{}) - timeout := time.Duration(timeOut) * time.Second - endtime := time.Now().Add(timeout) -outerfor: - for time.Now().Before(endtime) { - respData, err := p.doCMRequest("GET", as3DeployUrl, nil) - if err != nil { - return nil, err - } - err = json.Unmarshal(respData, &responseData) - if err != nil { - return nil, err - } - f5osLogger.Info("[getAS3DeploymentTaskStatus]", "Status:", hclog.Fmt("%+v", responseData["records"].([]interface{})[0].(map[string]interface{})["status"].(string))) - for _, v := range responseData["records"].([]interface{}) { - if v.(map[string]interface{})["status"].(string) == "failed" { - return nil, fmt.Errorf("%v", v.(map[string]interface{})["failure_reason"].(string)) - } - if v.(map[string]interface{})["status"].(string) != "completed" { - time.Sleep(5 * time.Second) - continue - } else { - break outerfor - } - } - } - f5osLogger.Info("[getAS3DeploymentTaskStatus]", "Response Result:", hclog.Fmt("%+v", responseData["response"])) - // .(map[string]interface{})["results"].([]interface{})[0].(map[string]interface{})["status"].(string))) - // if responseData["records"].([]interface{})[0].(map[string]interface{})["status"].(string) != "completed" { - // return nil, fmt.Errorf("AS3 service deployment failed with :%+v", responseData["records"].([]interface{})[0].(map[string]interface{})["status"].(string)) - // } - byteData, err := json.Marshal(responseData["app_data"].(map[string]interface{})) - if err != nil { - return nil, err - } - // appData := strings.Join([]string{strings.TrimSpace(string(byteData))}, "") - return string(byteData), nil -} - -// /api/v1/spaces/default/appsvcs/documents/83ff823d-477c-4666-a4c7-6b0563bb7be6/deployments/f1f55f4b-5bad-4f67-8ac2-83551502a7c8 -func (p *BigipNextCM) DeleteAS3DeploymentTask(docID string) error { - // as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s/%s", p.Host, uriAS3Root, "/documents", docID, "deployments", deployID) - as3DeployUrl := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) - f5osLogger.Info("[DeleteAS3DeploymentTask]", "URI Path", as3DeployUrl) - respData, err := p.doCMRequest("DELETE", as3DeployUrl, nil) - if err != nil { - return err - } - f5osLogger.Info("[DeleteAS3DeploymentTask]", "Data::", hclog.Fmt("%+v", string(respData))) - return nil -} - type DiscoverInstanceRequest struct { Address string `json:"address,omitempty"` Port int `json:"port,omitempty"` @@ -2398,145 +2328,6 @@ type TenantBackupRestoreTaskStatus struct { Status string `json:"status,omitempty"` } -// /device/v1/inventory?filter=address+eq+'10.10.10.10' - -func (p *BigipNextCM) GetDeviceIdByIp(deviceIp string) (deviceId *string, err error) { - deviceUrl := fmt.Sprintf("%s?filter=address+eq+'%s'", uriInventory, deviceIp) - f5osLogger.Debug("[GetDeviceInventory]", "URI Path", deviceUrl) - bigipNextDevice := &DeviceInventoryList{} - respData, err := p.GetCMRequest(deviceUrl) - if err != nil { - return nil, err - } - f5osLogger.Debug("[GetDeviceIdByIp]", "Requested BIG-IP Next:", hclog.Fmt("%+v", string(respData))) - json.Unmarshal(respData, bigipNextDevice) - if bigipNextDevice.Count == 1 { - deviceId := bigipNextDevice.Embedded.Devices[0].Id - return &deviceId, nil - } - return nil, fmt.Errorf("the requested device:%s, was not found", deviceIp) -} - -func (p *BigipNextCM) GetDeviceInfoByIp(deviceIp string) (deviceInfo interface{}, err error) { - deviceUrl := fmt.Sprintf("%s?filter=address+eq+'%s'", uriInventory, deviceIp) - f5osLogger.Debug("[GetDeviceInfoByIp]", "URI Path", deviceUrl) - respData, err := p.GetCMRequest(deviceUrl) - if err != nil { - return nil, err - } - f5osLogger.Debug("[GetDeviceInfoByIp]", "Requested BIG-IP Next:", hclog.Fmt("%+v", string(respData))) - deviceList := make(map[string]interface{}) - json.Unmarshal(respData, &deviceList) - if len(deviceList["_embedded"].(map[string]interface{})["devices"].([]interface{})) == 1 { - deviceInfo := deviceList["_embedded"].(map[string]interface{})["devices"].([]interface{})[0] - return deviceInfo, nil - } - return nil, fmt.Errorf("the requested device:%s, was not found", deviceIp) -} - -func (p *BigipNextCM) GetDeviceIdByHostname(deviceHostname string) (deviceId *string, err error) { - deviceUrl := fmt.Sprintf("%s?filter=hostname+eq+'%s'", uriInventory, deviceHostname) - f5osLogger.Info("[GetDeviceIdByHostname]", "URI Path", deviceUrl) - bigipNextDevice := &DeviceInventoryList{} - respData, err := p.GetCMRequest(deviceUrl) - if err != nil { - return nil, err - } - f5osLogger.Info("[GetDeviceIdByHostname]", "Resp BIG-IP Next:", hclog.Fmt("%+v", string(respData))) - err = json.Unmarshal(respData, &bigipNextDevice) - if err != nil { - return nil, err - } - - if bigipNextDevice.Count == 1 { - deviceId := bigipNextDevice.Embedded.Devices[0].Id - return &deviceId, nil - } - return nil, fmt.Errorf("the requested device:%s, was not found", deviceHostname) -} - -func (p *BigipNextCM) GetDeviceInfoByID(deviceId string) (interface{}, error) { - // deviceUrl := fmt.Sprintf("%s/%s", uriInventory, deviceId) - deviceUrl := fmt.Sprintf("%s/%s", uriDiscoverInstance, deviceId) - url := fmt.Sprintf("%s%s%s", p.Host, uriCMRoot, deviceUrl) - f5osLogger.Info("[GetDeviceInfoByID]", "Request path", hclog.Fmt("%+v", url)) - dataResource, err := p.doCMRequest("GET", url, nil) - if err != nil { - return nil, err - } - f5osLogger.Info("[GetDeviceInfoByID]", "Data::", hclog.Fmt("%+v", string(dataResource))) - var deviceInfo interface{} - err = json.Unmarshal(dataResource, &deviceInfo) - if err != nil { - return nil, err - } - return deviceInfo, nil -} - -// delete device from CM -func (p *BigipNextCM) DeleteDevice(deviceId string) error { - // deviceUrl := fmt.Sprintf("%s/%s", uriInventory, deviceId) - deviceUrl := fmt.Sprintf("%s/%s", uriDiscoverInstance, deviceId) - url := fmt.Sprintf("%s%s%s", p.Host, uriCMRoot, deviceUrl) - f5osLogger.Info("[DeleteDevice]", "Request path", hclog.Fmt("%+v", url)) - //{"save_backup":false} - var data = []byte(`{"save_backup":false}`) - respData, err := p.doCMRequest("DELETE", url, data) - // respData, err := p.DeleteCMRequest("DELETE", deviceUrl, data) - if err != nil { - return err - } - // {"_links":{"self":{"href":"/v1/deletion-tasks/02752890-5660-450c-ace9-b8e0a86a15ad"}},"path":"/v1/deletion-tasks/02752890-5660-450c-ace9-b8e0a86a15ad"} - respString := make(map[string]interface{}) - err = json.Unmarshal(respData, &respString) - if err != nil { - return err - } - f5osLogger.Info("[DeleteDevice]", "Task Path", hclog.Fmt("%+v", respString["path"].(string))) - //get task id from path - pathList := strings.Split(respString["path"].(string), "/") - taskId := pathList[len(pathList)-1] - f5osLogger.Info("[DeleteDevice]", "Task Id", hclog.Fmt("%+v", taskId)) - err = p.deleteTaskStatus(taskId) - if err != nil { - return err - } - f5osLogger.Info("[DeleteDevice]", "Data::", hclog.Fmt("%+v", string(respData))) - return nil -} - -// https://10.10.10.10/api/device/v1/deletion-tasks/02752890-5660-450c-ace9-b8e0a86a15ad -// verify device deletion task status -func (p *BigipNextCM) deleteTaskStatus(taskID string) error { - deviceUrl := fmt.Sprintf("%s/%s", "/device/v1/deletion-tasks", taskID) - url := fmt.Sprintf("%s%s%s", p.Host, uriCMRoot, deviceUrl) - f5osLogger.Info("[deleteTaskStatus]", "Request path", hclog.Fmt("%+v", url)) - timeout := 360 * time.Second - endtime := time.Now().Add(timeout) - respString := make(map[string]interface{}) - for time.Now().Before(endtime) { - respData, err := p.doCMRequest("GET", url, nil) - if err != nil { - return err - } - f5osLogger.Debug("[deleteTaskStatus]", "Data::", hclog.Fmt("%+v", string(respData))) - // {"_links":{"self":{"href":"/v1/deletion-tasks/642d5964-8cd9-4881-9086-1ed5ca682101"}},"address":"10.146.168.20","created":"2023-11-28T07:55:50.924918Z","device_id":"8d6c8c85-1738-4a34-b57b-d3644a2ecfcc","id":"642d5964-8cd9-4881-9086-1ed5ca682101","state":"factoryResetInstance","status":"running"} - err = json.Unmarshal(respData, &respString) - if err != nil { - return err - } - f5osLogger.Info("[deleteTaskStatus]", "Task Status", hclog.Fmt("%+v", respString["status"].(string))) - if respString["status"].(string) == "completed" { - return nil - } - if respString["status"].(string) == "failed" { - return fmt.Errorf("%s", respString) - } - time.Sleep(10 * time.Second) - } - return fmt.Errorf("%s", respString) -} - func (p *BigipNextCM) backupTenantTaskStatus(taskidPath string, timeOut int) (*TenantBackupRestoreTaskStatus, error) { taskData := &TenantBackupRestoreTaskStatus{} backupUrl := fmt.Sprintf("%s%s", "/device", taskidPath) @@ -2671,12 +2462,8 @@ func (p *BigipNextCM) DeleteBackupFile(fileName *string) error { return nil } -// https://10.192.75.131/api/device/v1/providers -// https://10.145.75.237 - func (p *BigipNextCM) GetDeviceProviders() ([]byte, error) { - uriProviders := "/device/v1/providers" - // providerUrl := fmt.Sprintf("%s", uriProviders) + // uriProviders := "/device/v1/providers" f5osLogger.Debug("[GetDeviceProviders]", "URI Path", uriProviders) respData, err := p.GetCMRequest(uriProviders) if err != nil { @@ -2699,115 +2486,6 @@ type DeviceProviderReq struct { CertFingerprint string `json:"cert_fingerprint,omitempty"` } `json:"connection,omitempty"` } -type DeviceProviderResponse struct { - Connection struct { - Authentication struct { - Type string `json:"type,omitempty"` - Username string `json:"username,omitempty"` - } `json:"authentication,omitempty"` - Host string `json:"host,omitempty"` - } `json:"connection,omitempty"` - Id string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` -} - -// https://10.145.75.237/api/device/v1/providers/vsphere -// Create POST call to create device provider -func (p *BigipNextCM) PostDeviceProvider(config interface{}) (*DeviceProviderResponse, error) { - uriProviders := "/device/v1/providers/vsphere" - if config.(*DeviceProviderReq).Type == "VELOS" || config.(*DeviceProviderReq).Type == "RSERIES" { - uriProviders = "/device/v1/providers/f5os" - //uriProviders = "/v1/spaces/default/providers/f5os" - } - providerUrl := fmt.Sprintf("%s", uriProviders) - f5osLogger.Debug("[PostDeviceProvider]", "URI Path", providerUrl) - f5osLogger.Debug("[PostDeviceProvider]", "Config", hclog.Fmt("%+v", config)) - body, err := json.Marshal(config) - if err != nil { - return nil, err - } - respData, err := p.PostCMRequest(providerUrl, body) - if err != nil { - if strings.Contains(err.Error(), "cert_fingerprint") { - config.(*DeviceProviderReq).Connection.CertFingerprint = strings.ReplaceAll(strings.Split(string(strings.Split(err.Error(), ",")[1]), ":")[2], "\"", "") - return p.PostDeviceProvider(config) - } - return nil, err - } - f5osLogger.Debug("[PostDeviceProvider]", "Resp::", hclog.Fmt("%+v", string(respData))) - var providerResp DeviceProviderResponse - err = json.Unmarshal(respData, &providerResp) - if err != nil { - return nil, err - } - return &providerResp, nil -} - -func (p *BigipNextCM) UpdateDeviceProvider(providerId string, config interface{}) (*DeviceProviderResponse, error) { - uriProviders := "/device/v1/providers/vsphere" - if config.(*DeviceProviderReq).Type == "VELOS" || config.(*DeviceProviderReq).Type == "RSERIES" { - uriProviders = "/device/v1/providers/f5os" - //uriProviders = "/v1/spaces/default/providers/f5os" - } - providerUrl := fmt.Sprintf("%s/%s", uriProviders, providerId) - f5osLogger.Debug("[UpdateDeviceProvider]", "URI Path", providerUrl) - f5osLogger.Debug("[UpdateDeviceProvider]", "Config", hclog.Fmt("%+v", config)) - body, err := json.Marshal(config) - if err != nil { - return nil, err - } - respData, err := p.PutCMRequest(providerUrl, body) - if err != nil { - return nil, err - } - f5osLogger.Debug("[UpdateDeviceProvider]", "Resp::", hclog.Fmt("%+v", string(respData))) - var providerResp DeviceProviderResponse - err = json.Unmarshal(respData, &providerResp) - if err != nil { - return nil, err - } - return &providerResp, nil -} - -func (p *BigipNextCM) GetDeviceProvider(providerId, providerType string) (*DeviceProviderResponse, error) { - uriProviders := "/device/v1/providers/vsphere" - if stringToUppercase(providerType) == "VELOS" || stringToUppercase(providerType) == "RSERIES" { - uriProviders = "/device/v1/providers/f5os" - //uriProviders = "/v1/spaces/default/providers/f5os" - } - providerUrl := fmt.Sprintf("%s/%s", uriProviders, providerId) - f5osLogger.Debug("[GetDeviceProvider]", "URI Path", providerUrl) - respData, err := p.GetCMRequest(providerUrl) - if err != nil { - return nil, err - } - f5osLogger.Info("[GetDeviceProvider]", "Data::", hclog.Fmt("%+v", string(respData))) - var providerResp DeviceProviderResponse - err = json.Unmarshal(respData, &providerResp) - if err != nil { - return nil, err - } - return &providerResp, nil -} - -// https://10.145.75.237/api/device/v1/providers/vsphere/85bc71c3-0bfc-4b28-bb86-13f7e1c1d7af -// Create function to delete device provider using provider id -func (p *BigipNextCM) DeleteDeviceProvider(providerId, providerType string) ([]byte, error) { - uriProviders := "/device/v1/providers/vsphere" - if stringToUppercase(providerType) == "VELOS" || stringToUppercase(providerType) == "RSERIES" { - uriProviders = "/device/v1/providers/f5os" - //uriProviders = "/v1/spaces/default/providers/f5os" - } - providerUrl := fmt.Sprintf("%s/%s", uriProviders, providerId) - f5osLogger.Debug("[DeleteDeviceProvider]", "URI Path", providerUrl) - respData, err := p.DeleteCMRequest(providerUrl) - if err != nil { - return nil, err - } - f5osLogger.Info("[DeleteDeviceProvider]", "Data::", hclog.Fmt("%+v", string(respData))) - return respData, nil -} func stringToUppercase(str string) string { return strings.ToUpper(str) @@ -2958,254 +2636,22 @@ func stringToUppercase(str string) string { // return &cmReqDeviceInstance, nil // } -type CMReqRseriesProperties struct { - TenantImageName string `json:"tenant_image_name"` - TenantDeploymentFile string `json:"tenant_deployment_file"` - VlanIds []int `json:"vlan_ids"` - DiskSize int `json:"disk_size"` - CpuCores int `json:"cpu_cores"` - // ManagementAddress string `json:"management_address"` - // ManagementNetworkWidth int `json:"management_network_width"` - // L1Networks []string `json:"l1Networks"` - // ManagementCredentials struct { - // Username string `json:"username"` - // Password string `json:"password"` - // } `json:"management_credentials"` - // InstanceOneTimePassword string `json:"instance_one_time_password"` - // Hostname string `json:"hostname"` -} - -type CMReqVelosProperties struct { - TenantImageName string `json:"tenant_image_name"` - TenantDeploymentFile string `json:"tenant_deployment_file"` - VlanIds []int `json:"vlan_ids"` - SlotIds []int `json:"slot_ids"` - DiskSize int `json:"disk_size"` - CpuCores int `json:"cpu_cores"` -} - -type CMReqDeviceInstance struct { - TemplateName string `json:"template_name,omitempty"` - Parameters struct { - InstantiationProvider []CMReqInstantiationProvider `json:"instantiation_provider,omitempty"` - VSphereProperties []CMReqVsphereProperties `json:"vSphere_properties,omitempty"` - VsphereNetworkAdapterSettings []CMReqVsphereNetworkAdapterSettings `json:"vsphere_network_adapter_settings,omitempty"` - RseriesProperties []CMReqRseriesProperties `json:"rseries_properties,omitempty"` - VelosProperties []CMReqVelosProperties `json:"velos_properties,omitempty"` - DnsServers []string `json:"dns_servers,omitempty"` - NtpServers []string `json:"ntp_servers,omitempty"` - ManagementAddress string `json:"management_address,omitempty"` - ManagementNetworkWidth int `json:"management_network_width,omitempty"` - DefaultGateway string `json:"default_gateway,omitempty"` - L1Networks []CMReqL1Networks `json:"l1Networks,omitempty"` - ManagementCredentialsUsername string `json:"management_credentials_username,omitempty"` - ManagementCredentialsPassword string `json:"management_credentials_password,omitempty"` - InstanceOneTimePassword string `json:"instance_one_time_password,omitempty"` - Hostname string `json:"hostname,omitempty"` - } `json:"parameters,omitempty"` -} - -type CMReqInstantiationProvider struct { - Id string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` -} -type CMReqVsphereProperties struct { - NumCpus int `json:"num_cpus,omitempty"` - Memory int `json:"memory,omitempty"` - DatacenterName string `json:"datacenter_name,omitempty"` - ClusterName string `json:"cluster_name,omitempty"` - DatastoreName string `json:"datastore_name,omitempty"` - ResourcePoolName string `json:"resource_pool_name,omitempty"` - VsphereContentLibrary string `json:"vsphere_content_library,omitempty"` - VmTemplateName string `json:"vm_template_name,omitempty"` -} -type CMReqVsphereNetworkAdapterSettings struct { - InternalNetworkName string `json:"internal_network_name,omitempty"` - HaDataPlaneNetworkName string `json:"ha_data_plane_network_name,omitempty"` - HaControlPlaneNetworkName string `json:"ha_control_plane_network_name,omitempty"` - MgmtNetworkName string `json:"mgmt_network_name,omitempty"` - ExternalNetworkName string `json:"external_network_name,omitempty"` -} - -type CMReqSelfIps struct { - Address string `json:"address,omitempty"` - DeviceName string `json:"deviceName,omitempty"` -} - -type CMReqVlans struct { - SelfIps []CMReqSelfIps `json:"selfIps,omitempty"` - Name string `json:"name,omitempty"` - Tag int `json:"tag,omitempty"` - DefaultVrf bool `json:"defaultVrf,omitempty"` -} - -type CMReqL1Networks struct { - Vlans []CMReqVlans `json:"vlans,omitempty"` - L1Link struct { - LinkType string `json:"linkType,omitempty"` - Name string `json:"name,omitempty"` - } `json:"l1Link,omitempty"` - Name string `json:"name,omitempty"` -} - -type CMReqDeviceInstanceBackup struct { - TemplateName string `json:"template_name,omitempty"` - Parameters struct { - InstantiationProvider []CMReqInstantiationProvider `json:"instantiation_provider,omitempty"` - VSphereProperties []CMReqVsphereProperties `json:"vSphere_properties,omitempty"` - VsphereNetworkAdapterSettings []CMReqVsphereNetworkAdapterSettings `json:"vsphere_network_adapter_settings,omitempty"` - DnsServers []string `json:"dns_servers,omitempty"` - NtpServers []string `json:"ntp_servers,omitempty"` - ManagementAddress string `json:"management_address,omitempty"` - ManagementNetworkWidth int `json:"management_network_width,omitempty"` - DefaultGateway string `json:"default_gateway,omitempty"` - L1Networks []CMReqL1Networks `json:"l1Networks,omitempty"` - ManagementCredentialsUsername string `json:"management_credentials_username,omitempty"` - ManagementCredentialsPassword string `json:"management_credentials_password,omitempty"` - InstanceOneTimePassword string `json:"instance_one_time_password,omitempty"` - Hostname string `json:"hostname,omitempty"` - } `json:"parameters,omitempty"` -} - -func (p *BigipNextCM) PostDeviceInstance(config *CMReqDeviceInstance, timeout int) ([]byte, error) { - uriInstances := "/device/v1/instances" - instanceUrl := fmt.Sprintf("%s", uriInstances) - f5osLogger.Debug("[PostDeviceInstance]", "URI Path", instanceUrl) - f5osLogger.Debug("[PostDeviceInstance]", "Config", hclog.Fmt("%+v", config)) - body, err := json.Marshal(config) - if err != nil { - return nil, err - } - respData, err := p.PostCMRequest(instanceUrl, body) - if err != nil { - return nil, err - } - f5osLogger.Debug("[PostDeviceInstance]", "Data::", hclog.Fmt("%+v", string(respData))) - // {"_links":{"self":{"href":"/v1/instances/tasks/deacca61-3162-4672-aac8-2d6bd2b69438"}},"path":"/v1/instances/tasks/deacca61-3162-4672-aac8-2d6bd2b69438"} - respString := make(map[string]interface{}) - err = json.Unmarshal(respData, &respString) - if err != nil { - return nil, err - } - f5osLogger.Debug("[PostDeviceInstance]", "Task Path", hclog.Fmt("%+v", respString["path"].(string))) - // split path string to get task id - taskId := strings.Split(respString["path"].(string), "/") - f5osLogger.Info("[PostDeviceInstance]", "Task Id", hclog.Fmt("%+v", taskId[len(taskId)-1])) - // get task status - taskData, err := p.GetDeviceInstanceTaskStatus(taskId[len(taskId)-1], timeout) - if err != nil { - return nil, err - } - f5osLogger.Info("[PostDeviceInstance]", "Task Status", hclog.Fmt("%+v", taskData)) - return respData, nil -} +// [ +// { +// "digitalAssetId": "d290f1ee-6c54-4b01-90e6-d701748f0851", +// "jwtId": "d390f1ee-6c54-4b01-90e6-d701748f0851" +// } +// ] -// /v1/instances/tasks/deacca61-3162-4672-aac8-2d6bd2b69438 -// get device instance task status -func (p *BigipNextCM) GetDeviceInstanceTaskStatus(taskID string, timeOut int) (map[string]interface{}, error) { - // taskData := &DeviceInstanceTaskStatus{} - taskData := make(map[string]interface{}) - instanceUrl := fmt.Sprintf("%s%s", "/device/v1/instances/tasks/", taskID) - f5osLogger.Debug("[GetDeviceInstanceTaskStatus]", "URI Path", instanceUrl) - // var timeout time.Duration - timeout := time.Duration(timeOut) * time.Second - endtime := time.Now().Add(timeout) - for time.Now().Before(endtime) { - respData, err := p.GetCMRequest(instanceUrl) - if err != nil { - return nil, err - } - f5osLogger.Info("[GetDeviceInstanceTaskStatus]", "Task Status:\t", hclog.Fmt("%+v", string(respData))) - err = json.Unmarshal(respData, &taskData) - if err != nil { - return nil, err - } - if taskData["status"] == "completed" { - return taskData, nil +func Contains[T comparable](s []T, e T) bool { + for _, v := range s { + if v == e { + return true } - if taskData["status"] == "failed" { - return nil, fmt.Errorf("%s", taskData["failure_reason"]) - } - inVal := timeOut / 10 - time.Sleep(time.Duration(inVal) * time.Second) } - return nil, fmt.Errorf("task Status is still in :%+v within timeout period of:%+v", taskData["status"], timeout) -} - -// // convert a string to byte array -// func stringToByteArray(str string) []byte { -// return []byte(str) -// } - -func (p *BigipNextCM) GetDeviceProviderIDByHostname(hostname string) (interface{}, error) { - uriProviders := "/device/v1/providers" - providerUrl := fmt.Sprintf("%s?filter=name+eq+'%s'", uriProviders, hostname) - f5osLogger.Info("[GetDeviceProviderIDByHostname]", "URI Path", providerUrl) - respData, err := p.GetCMRequest(providerUrl) - if err != nil { - return nil, err - } - f5osLogger.Info("[GetDeviceProviderIDByHostname]", "Requested Providers:", hclog.Fmt("%+v", string(respData))) - var providerResp []interface{} - err = json.Unmarshal(respData, &providerResp) - if err != nil { - return nil, err - } - if len(providerResp) == 1 && providerResp[0].(map[string]interface{})["provider_name"].(string) == hostname { - return providerResp[0].(map[string]interface{})["provider_id"].(string), nil - } - return nil, fmt.Errorf("failed to get ID for provider: %+v", hostname) -} - -// https://10.145.75.237/api/llm/license/a2064013-659d-4de0-8c22-773d21414885/status -// get device license status -func (p *BigipNextCM) GetDeviceLicenseStatus(deviceId *string) ([]byte, error) { - uriLicense := "/llm/license" - licenseUrl := fmt.Sprintf("%s/%s/status", uriLicense, *deviceId) - f5osLogger.Debug("[GetDeviceLicenseStatus]", "URI Path", licenseUrl) - respData, err := p.GetCMRequest(licenseUrl) - if err != nil { - return nil, err - } - f5osLogger.Debug("[GetDeviceLicenseStatus]", "Requested License Status:", hclog.Fmt("%+v", string(respData))) - return respData, nil -} - -func encodeUrl(urlName string) string { - // Encode the urlName - urlNameEncoded := url.QueryEscape(urlName) - return urlNameEncoded + return false } -// #################### -// POST -// https://10.145.75.237/api/llm/tasks/token/verify -// create post call to token verify - -// func (p *BigipNextCM) PostTokenVerify(config *TokenVerify) ([]byte, error) { -// uriToken := "/llm/tasks/token/verify" -// tokenUrl := fmt.Sprintf("%s", uriToken) -// f5osLogger.Debug("[PostTokenVerify]", "URI Path", tokenUrl) -// f5osLogger.Debug("[PostTokenVerify]", "Config", hclog.Fmt("%+v", config)) -// body, err := json.Marshal(config) -// if err != nil { -// return nil, err -// } -// respData, err := p.PostCMRequest(tokenUrl, body) -// if err != nil { -// return nil, err -// } -// f5osLogger.Debug("[PostTokenVerify]", "Data::", hclog.Fmt("%+v", string(respData))) -// return respData, nil -// } - -// create TokenVerify struct with below payload -//{"jwt":"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InYxIiwiamt1IjoiaHR0cHM6Ly9wcm9kdWN0LmFwaXMuZjUuY29tL2VlL3Y.eyJzdWIiOiJGTkktNzYyNmVmM2QtNTc4ZC00MGU1LWI5YjYtZDI3ZmRkZTRlODBkIiwiaWF0IjoxNjQ3NDQ1NjMxLCJpc3MiOiJGNSBJbmMuIiwiYXVkIjoidXJuOmY1OnRlZW0iLCJqdGkiOiI1N2VmM2ZmMC1hNTQwLTExZWMtYjE3Ny1lYmRiMzNmYmNjZmYiLCJmNV9vcmRlcl90eXBlIjoiZXZhbCIsImY1X29yZGVyX3N1YnR5cGUiOiJ0cmlhbCJ9.cBfrqxn09rTGiSKpIu6PpDZoCKOY2BRtm6Q9xfAf0iv6IdY3YZn3iqSR1Qrl5Wgwx1uEDsNasFELdvynAQ1vDTG0QNFgSR5HKC9rFS_QBXK8G2XZuJr_XLQxKeOztzbYTn1V2aoVBeZXawcQG9YVu_MXdkDG2LL7LhWgXWVyckuF99cW1ndwsbucx2nXW7-fcU_TsDnTryt8nwQi0hiw-0DlYXEVYfHxndg_JNRlNtKL8aAgf5rUACrhQTVag7in_UuGV7jhKIk5SjVR2-lUnKA2w3Oo6WCeJv9DyIULWfkJwasBlyF9hiYiMUiTyaW7MK-Kx0w9IamlYy0KBzepFvUsUfYIsRJUnqjFHn_S1Rcg7cGiJyl4XUtVP0LKB80xfxYN2ThAiW7usNSAchepSbUXHatxyWWZavxTu1B48tQmiwBb6_OFSxw_GP1SOlE5v539uObPsJ7cTA-OiWby3VgaU4SgHuLg_ITlwcSc3FSZnQY4qYcu9k8nbhbx-2UmN6C0lkaW5ha2xe08kJWXdPzQQ3Y1bJb9T5IXWeGdGb_ppqHsV7LzuEVZJUACOVFvqXDnJPXggLnI8G_w1aCWLWpxZeRpY7iqWjVGzD5cD_eAwLYoSEUtSyG83dbRSZeDXFQSkZ6ZuLz94iySLZPR98Mi0rLfwpRlkIXBXZ_ZMDs"} - -// OUTPUT: {"isValid":true} -// #################### - // #################### // POST // https://10.145.75.237/api/llm/token @@ -3758,6 +3204,116 @@ func (p *BigipNextCM) DeleteCMHANodes(deleteNodes []string) { } } +type CMExternalStorageUser struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +type CMExternalStorage struct { + StorageType string `json:"storage_type,omitempty"` + StorageAddress string `json:"storage_address,omitempty"` + StorageSharePath string `json:"storage_share_path,omitempty"` + StorageShareDir string `json:"storage_share_dir"` + StorageUser *CMExternalStorageUser `json:"storage_user,omitempty"` +} + +type CMExternalStorageStatus struct { + Setup string `json:"setup,omitempty"` + FailureMessage string `json:"failure_message,omitempty"` +} + +type CMExternalStorageResp struct { + Spec CMExternalStorage `json:"spec,omitempty"` + Status CMExternalStorageStatus `json:"status,omitempty"` +} + +type BootstrapCMResp struct { + Created string `json:"created,omitempty"` + Status string `json:"status,omitempty"` + Step string `json:"step,omitempty"` + Updated string `json:"updated,omitempty"` +} + +func (p *BigipNextCM) AddExternalStorage(extStorage *CMExternalStorage) (string, error) { + uriStorage := "/v1/system/infra/external-storage" + + payload, err := json.Marshal(extStorage) + + if err != nil { + f5osLogger.Error("[AddExternalStorage]", "Error", err) + return "", err + } + + resp, err := p.PostCMRequest(uriStorage, payload) + + return string(resp), err +} + +func (p *BigipNextCM) BootstrapCM(timeout int64) (string, error) { + uriBootstrap := "/v1/system/infra/bootstrap" + var res BootstrapCMResp + + resp, err := p.PostCMRequest(uriBootstrap, nil) + + if err != nil { + f5osLogger.Error("[BootstrapCM]", "Error", err) + return "", err + } + + json.Unmarshal(resp, &res) + + start := time.Now() + + for res.Status == "RUNNING" && time.Since(start) < (time.Second*time.Duration(timeout)) { + resp, err = p.GetCMRequest(uriBootstrap) + + if err != nil { + if strings.HasPrefix(err.Error(), `{"code":50`) { + time.Sleep(3 * time.Second) + continue + } + + f5osLogger.Error("[BootstrapCM]", "Error", err) + return "", err + } + + json.Unmarshal(resp, &res) + + if res.Status == "COMPLETED" { + break + } + + if res.Status == "FAILED" { + return "", fmt.Errorf("bootstrap failed: %v", res.Step) + } + + f5osLogger.Info("[BootstrapCM]", "Info", fmt.Sprintf("CM Bootstrap status: %v", res.Step)) + time.Sleep(5 * time.Second) + } + + return string(resp), err +} + +func (p *BigipNextCM) GetCMBootstrap() (string, error) { + uriBootstrap := "/v1/system/infra/bootstrap" + + resp, err := p.GetCMRequest(uriBootstrap) + + if err != nil { + return "", err + } + + return string(resp), nil +} + +func (p *BigipNextCM) GetCMExternalStorage() (string, error) { + uriStorage := "/v1/system/infra/external-storage" + + resp, err := p.GetCMRequest(uriStorage) + + return string(resp), err +} + // https://10.144.73.240/api/device/v1/instances // Req: diff --git a/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/cmappmgr.go b/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/cmappmgr.go new file mode 100644 index 0000000..c6e8d1b --- /dev/null +++ b/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/cmappmgr.go @@ -0,0 +1,175 @@ +/* +Copyright 2023 F5 Networks Inc. +This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +// Package bigipnext interacts with BIGIP-NEXT/CM systems using the OPEN API. +package bigipnext + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/hashicorp/go-hclog" +) + +const ( + uriAS3Root = "/api/v1/spaces/default/appsvcs" +) + +// /api/v1/spaces/default/appsvcs/documents +func (p *BigipNextCM) PostAS3DraftDocument(config string) (string, error) { + as3DraftURL := fmt.Sprintf("%s%s%s", p.Host, uriAS3Root, "/documents") + f5osLogger.Info("[PostAS3DraftDocument]", "URI Path", as3DraftURL) + f5osLogger.Info("[PostAS3DraftDocument]", "Config", hclog.Fmt("%+v", config)) + respData, err := p.doCMRequest("POST", as3DraftURL, []byte(config)) + if err != nil { + return "", err + } + f5osLogger.Info("[PostAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) + //{"Message":"Application service created successfully","_links":{"self":{"href":"/api/v1/spaces/default/appsvcs/documents/3a220683-6527-4443-8da7-279680c21ac5"}},"id":"3a220683-6527-4443-8da7-279680c21ac5"} + respString := make(map[string]interface{}) + err = json.Unmarshal(respData, &respString) + if err != nil { + return "", err + } + f5osLogger.Info("[PostAS3DraftDocument]", "Document Drart", hclog.Fmt("%+v", respString["id"].(string))) + return respString["id"].(string), nil +} + +// /api/v1/spaces/default/appsvcs/documents/3a220683-6527-4443-8da7-279680c21ac5 +func (p *BigipNextCM) GetAS3DraftDocument(docID string) ([]byte, error) { + as3DraftURL := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) + f5osLogger.Info("[GetAS3DraftDocument]", "URI Path", as3DraftURL) + respData, err := p.doCMRequest("GET", as3DraftURL, nil) + if err != nil { + return []byte(""), err + } + f5osLogger.Info("[GetAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) + return respData, nil +} + +func (p *BigipNextCM) PutAS3DraftDocument(docID, config string) error { + as3DraftURL := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) + f5osLogger.Info("[PutAS3DraftDocument]", "URI Path", as3DraftURL) + f5osLogger.Info("[PutAS3DraftDocument]", "Config", hclog.Fmt("%+v", config)) + respData, err := p.doCMRequest("PUT", as3DraftURL, []byte(config)) + if err != nil { + return err + } + f5osLogger.Info("[PutAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) + return nil +} + +// /api/v1/spaces/default/appsvcs/documents/3a220683-6527-4443-8da7-279680c21ac5 +func (p *BigipNextCM) DeleteAS3DraftDocument(docID string) error { + as3DraftURL := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) + f5osLogger.Info("[DeleteAS3DraftDocument]", "URI Path", as3DraftURL) + respData, err := p.doCMRequest("DELETE", as3DraftURL, nil) + if err != nil { + return err + } + f5osLogger.Info("[DeleteAS3DraftDocument]", "Data::", hclog.Fmt("%+v", string(respData))) + return nil +} + +// https://clouddocs.f5.com/api/v1/spaces/default/appsvcs/documents/{document-id}/deployments +func (p *BigipNextCM) CMAS3DeployNext(draftID, target string, timeOut int) (string, error) { + as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s", p.Host, uriAS3Root, "/documents", draftID, "deployments") + f5osLogger.Info("[CMAS3DeployNext]", "URI Path", as3DeployUrl) + as3Json := make(map[string]interface{}) + as3Json["target"] = target + as3data, err := json.Marshal(as3Json) + if err != nil { + return "", err + } + f5osLogger.Info("[CMAS3DeployNext]", "Data::", hclog.Fmt("%+v", string(as3data))) + respData, err := p.doCMRequest("POST", as3DeployUrl, as3data) + if err != nil { + return "", err + } + f5osLogger.Info("[CMAS3DeployNext]", "Data::", hclog.Fmt("%+v", string(respData))) + //{ "Message": "Deployment task created successfully", "_links": { "self": { "href": "/declare/1a5a6049-8220-483a-8cbc-275a4b190d35/deployments/2ceb048a-0ee6-4a2d-8952-cd15583bb5e8" } }, "id": "2ceb048a-0ee6-4a2d-8952-cd15583bb5e8" } + respString := make(map[string]interface{}) + err = json.Unmarshal(respData, &respString) + if err != nil { + return "", err + } + f5osLogger.Info("[CMAS3DeployNext]", "Deployment Task", hclog.Fmt("%+v", respString["id"].(string))) + _, err = p.getAS3DeploymentTaskStatus(draftID, respString["id"].(string), timeOut) + if err != nil { + return "", err + } + return respString["id"].(string), nil +} + +// https://clouddocs.f5.com/api/v1/spaces/default/appsvcs/documents/{document-id}/deployments/{deployment-id} +func (p *BigipNextCM) GetAS3DeploymentTaskStatus(docID, deployID string) (interface{}, error) { + as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s/%s", p.Host, uriAS3Root, "/documents", docID, "deployments", deployID) + f5osLogger.Info("[GetAS3DeploymentTaskStatus]", "URI Path", as3DeployUrl) + return p.getAS3DeploymentTaskStatus(docID, deployID, 60) +} + +// https://clouddocs.f5.com/api/v1/spaces/default/appsvcs/documents/{document-id}/deployments/{deployment-id} +func (p *BigipNextCM) getAS3DeploymentTaskStatus(docID, deployID string, timeOut int) (interface{}, error) { + as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s/%s", p.Host, uriAS3Root, "/documents", docID, "deployments", deployID) + f5osLogger.Info("[getAS3DeploymentTaskStatus]", "URI Path", as3DeployUrl) + responseData := make(map[string]interface{}) + timeout := time.Duration(timeOut) * time.Second + endtime := time.Now().Add(timeout) +outerfor: + for time.Now().Before(endtime) { + respData, err := p.doCMRequest("GET", as3DeployUrl, nil) + if err != nil { + return nil, err + } + f5osLogger.Info("[getAS3DeploymentTaskStatus]", "respData:", hclog.Fmt("%+v", string(respData))) + err = json.Unmarshal(respData, &responseData) + if err != nil { + return nil, err + } + f5osLogger.Info("[getAS3DeploymentTaskStatus]", "Status:", hclog.Fmt("%+v", responseData["records"].([]interface{})[0].(map[string]interface{})["status"].(string))) + for _, v := range responseData["records"].([]interface{}) { + if v.(map[string]interface{})["status"].(string) == "failed" { + return nil, fmt.Errorf("%v", v.(map[string]interface{})["failure_reason"].(string)) + } + if v.(map[string]interface{})["status"].(string) != "completed" { + time.Sleep(5 * time.Second) + continue + } else { + break outerfor + } + } + } + for _, v := range responseData["response"].(map[string]interface{})["results"].([]interface{}) { + if v.(map[string]interface{})["message"].(string) == "failed" { + tenantName := v.(map[string]interface{})["tenant"].(string) + return nil, fmt.Errorf("%v deployment failed", tenantName) + } + } + f5osLogger.Info("[getAS3DeploymentTaskStatus]", "Response Result:", hclog.Fmt("%+v", responseData["response"])) + // .(map[string]interface{})["results"].([]interface{})[0].(map[string]interface{})["status"].(string))) + // if responseData["records"].([]interface{})[0].(map[string]interface{})["status"].(string) != "completed" { + // return nil, fmt.Errorf("AS3 service deployment failed with :%+v", responseData["records"].([]interface{})[0].(map[string]interface{})["status"].(string)) + // } + byteData, err := json.Marshal(responseData["request"].(map[string]interface{})) + if err != nil { + return nil, err + } + // appData := strings.Join([]string{strings.TrimSpace(string(byteData))}, "") + return string(byteData), nil +} + +// /api/v1/spaces/default/appsvcs/documents/83ff823d-477c-4666-a4c7-6b0563bb7be6/deployments/f1f55f4b-5bad-4f67-8ac2-83551502a7c8 +func (p *BigipNextCM) DeleteAS3DeploymentTask(docID string) error { + // as3DeployUrl := fmt.Sprintf("%s%s%s/%s/%s/%s", p.Host, uriAS3Root, "/documents", docID, "deployments", deployID) + as3DeployUrl := fmt.Sprintf("%s%s%s/%s", p.Host, uriAS3Root, "/documents", docID) + f5osLogger.Info("[DeleteAS3DeploymentTask]", "URI Path", as3DeployUrl) + respData, err := p.doCMRequest("DELETE", as3DeployUrl, nil) + if err != nil { + return err + } + f5osLogger.Info("[DeleteAS3DeploymentTask]", "Data::", hclog.Fmt("%+v", string(respData))) + return nil +} diff --git a/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/cminstance.go b/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/cminstance.go new file mode 100644 index 0000000..4efdb5f --- /dev/null +++ b/vendor/gitswarm.f5net.com/terraform-providers/bigipnext/cminstance.go @@ -0,0 +1,708 @@ +/* +Copyright 2024 F5 Networks Inc. +This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. +*/ +// Package bigipnext interacts with BIGIP-NEXT/CM systems using the OPEN API. +package bigipnext + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "time" + + "github.com/hashicorp/go-hclog" +) + +const ( + uriInventory = "/device/v1/inventory" + uriProviders = "/v1/spaces/default/providers" + uriDiscoverInstance = "/v1/spaces/default/instances" + uriLicense = "/v1/spaces/default/instances/license" + // uriLicenseActivate = "/v1/spaces/default/instances/license/activate" +) + +type DeviceInventoryList struct { + Embedded struct { + Devices []struct { + Links struct { + Self struct { + Href string `json:"href"` + } `json:"self"` + } `json:"_links"` + Address string `json:"address"` + CertificateValidated time.Time `json:"certificate_validated"` + CertificateValidationError string `json:"certificate_validation_error"` + CertificateValidity bool `json:"certificate_validity"` + Hostname string `json:"hostname"` + Id string `json:"id"` + Mode string `json:"mode"` + PlatformType string `json:"platform_type"` + Port int `json:"port"` + Version string `json:"version"` + } `json:"devices"` + } `json:"_embedded"` + Count int `json:"count"` + Total int `json:"total"` +} + +type CMReqRseriesProperties struct { + TenantImageName string `json:"tenant_image_name"` + TenantDeploymentFile string `json:"tenant_deployment_file"` + VlanIds []int `json:"vlan_ids"` + DiskSize int `json:"disk_size"` + CpuCores int `json:"cpu_cores"` + // ManagementAddress string `json:"management_address"` + // ManagementNetworkWidth int `json:"management_network_width"` + // L1Networks []string `json:"l1Networks"` + // ManagementCredentials struct { + // Username string `json:"username"` + // Password string `json:"password"` + // } `json:"management_credentials"` + // InstanceOneTimePassword string `json:"instance_one_time_password"` + // Hostname string `json:"hostname"` +} + +type CMReqVelosProperties struct { + TenantImageName string `json:"tenant_image_name"` + TenantDeploymentFile string `json:"tenant_deployment_file"` + VlanIds []int `json:"vlan_ids"` + SlotIds []int `json:"slot_ids"` + DiskSize int `json:"disk_size"` + CpuCores int `json:"cpu_cores"` +} + +type CMReqInstantiationProvider struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` +} +type CMReqVsphereProperties struct { + NumCpus int `json:"num_cpus,omitempty"` + Memory int `json:"memory,omitempty"` + DatacenterName string `json:"datacenter_name,omitempty"` + ClusterName string `json:"cluster_name,omitempty"` + DatastoreName string `json:"datastore_name,omitempty"` + ResourcePoolName string `json:"resource_pool_name,omitempty"` + VsphereContentLibrary string `json:"vsphere_content_library,omitempty"` + VmTemplateName string `json:"vm_template_name,omitempty"` +} +type CMReqVsphereNetworkAdapterSettings struct { + InternalNetworkName string `json:"internal_network_name,omitempty"` + HaDataPlaneNetworkName string `json:"ha_data_plane_network_name,omitempty"` + HaControlPlaneNetworkName string `json:"ha_control_plane_network_name,omitempty"` + MgmtNetworkName string `json:"mgmt_network_name,omitempty"` + ExternalNetworkName string `json:"external_network_name,omitempty"` +} + +type CMReqSelfIps struct { + Address string `json:"address,omitempty"` + DeviceName string `json:"deviceName,omitempty"` +} + +type CMReqVlans struct { + SelfIps []CMReqSelfIps `json:"selfIps,omitempty"` + Name string `json:"name,omitempty"` + Tag int `json:"tag,omitempty"` + DefaultVrf bool `json:"defaultVrf,omitempty"` +} + +type CMReqL1Networks struct { + Vlans []CMReqVlans `json:"vlans,omitempty"` + L1Link struct { + LinkType string `json:"linkType,omitempty"` + Name string `json:"name,omitempty"` + } `json:"l1Link,omitempty"` + Name string `json:"name,omitempty"` +} + +type CMReqDeviceInstance struct { + TemplateName string `json:"template_name,omitempty"` + Parameters struct { + InstantiationProvider []CMReqInstantiationProvider `json:"instantiation_provider,omitempty"` + VSphereProperties []CMReqVsphereProperties `json:"vSphere_properties,omitempty"` + VsphereNetworkAdapterSettings []CMReqVsphereNetworkAdapterSettings `json:"vsphere_network_adapter_settings,omitempty"` + RseriesProperties []CMReqRseriesProperties `json:"rseries_properties,omitempty"` + VelosProperties []CMReqVelosProperties `json:"velos_properties,omitempty"` + DnsServers []string `json:"dns_servers,omitempty"` + NtpServers []string `json:"ntp_servers,omitempty"` + ManagementAddress string `json:"management_address,omitempty"` + ManagementNetworkWidth int `json:"management_network_width,omitempty"` + DefaultGateway string `json:"default_gateway,omitempty"` + L1Networks []CMReqL1Networks `json:"l1Networks,omitempty"` + ManagementCredentialsUsername string `json:"management_credentials_username,omitempty"` + ManagementCredentialsPassword string `json:"management_credentials_password,omitempty"` + InstanceOneTimePassword string `json:"instance_one_time_password,omitempty"` + Hostname string `json:"hostname,omitempty"` + } `json:"parameters,omitempty"` +} + +type CMReqDeviceInstanceBackup struct { + TemplateName string `json:"template_name,omitempty"` + Parameters struct { + InstantiationProvider []CMReqInstantiationProvider `json:"instantiation_provider,omitempty"` + VSphereProperties []CMReqVsphereProperties `json:"vSphere_properties,omitempty"` + VsphereNetworkAdapterSettings []CMReqVsphereNetworkAdapterSettings `json:"vsphere_network_adapter_settings,omitempty"` + DnsServers []string `json:"dns_servers,omitempty"` + NtpServers []string `json:"ntp_servers,omitempty"` + ManagementAddress string `json:"management_address,omitempty"` + ManagementNetworkWidth int `json:"management_network_width,omitempty"` + DefaultGateway string `json:"default_gateway,omitempty"` + L1Networks []CMReqL1Networks `json:"l1Networks,omitempty"` + ManagementCredentialsUsername string `json:"management_credentials_username,omitempty"` + ManagementCredentialsPassword string `json:"management_credentials_password,omitempty"` + InstanceOneTimePassword string `json:"instance_one_time_password,omitempty"` + Hostname string `json:"hostname,omitempty"` + } `json:"parameters,omitempty"` +} + +type DeviceProviderResponse struct { + Connection struct { + Authentication struct { + Type string `json:"type,omitempty"` + Username string `json:"username,omitempty"` + } `json:"authentication,omitempty"` + Host string `json:"host,omitempty"` + } `json:"connection,omitempty"` + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` +} + +// /device/v1/inventory?filter=address+eq+'10.10.10.10' +func (p *BigipNextCM) GetDeviceIdByIp(deviceIp string) (deviceId *string, err error) { + deviceUrl := fmt.Sprintf("%s?filter=address+eq+'%s'", uriInventory, deviceIp) + f5osLogger.Debug("[GetDeviceInventory]", "URI Path", deviceUrl) + bigipNextDevice := &DeviceInventoryList{} + respData, err := p.GetCMRequest(deviceUrl) + if err != nil { + return nil, err + } + f5osLogger.Debug("[GetDeviceIdByIp]", "Requested BIG-IP Next:", hclog.Fmt("%+v", string(respData))) + json.Unmarshal(respData, bigipNextDevice) + if bigipNextDevice.Count == 1 { + deviceId := bigipNextDevice.Embedded.Devices[0].Id + return &deviceId, nil + } + return nil, fmt.Errorf("the requested device:%s, was not found", deviceIp) +} + +func (p *BigipNextCM) GetDeviceInfoByIp(deviceIp string) (deviceInfo interface{}, err error) { + deviceUrl := fmt.Sprintf("%s?filter=address+eq+'%s'", uriInventory, deviceIp) + f5osLogger.Debug("[GetDeviceInfoByIp]", "URI Path", deviceUrl) + respData, err := p.GetCMRequest(deviceUrl) + if err != nil { + return nil, err + } + f5osLogger.Debug("[GetDeviceInfoByIp]", "Requested BIG-IP Next:", hclog.Fmt("%+v", string(respData))) + deviceList := make(map[string]interface{}) + json.Unmarshal(respData, &deviceList) + if len(deviceList["_embedded"].(map[string]interface{})["devices"].([]interface{})) == 1 { + deviceInfo := deviceList["_embedded"].(map[string]interface{})["devices"].([]interface{})[0] + return deviceInfo, nil + } + return nil, fmt.Errorf("the requested device:%s, was not found", deviceIp) +} + +func (p *BigipNextCM) GetDeviceInfoByID(deviceId string) (interface{}, error) { + // deviceUrl := fmt.Sprintf("%s/%s", uriInventory, deviceId) + deviceUrl := fmt.Sprintf("%s/%s", uriDiscoverInstance, deviceId) + url := fmt.Sprintf("%s%s%s", p.Host, uriCMRoot, deviceUrl) + f5osLogger.Info("[GetDeviceInfoByID]", "Request path", hclog.Fmt("%+v", url)) + dataResource, err := p.doCMRequest("GET", url, nil) + if err != nil { + return nil, err + } + f5osLogger.Info("[GetDeviceInfoByID]", "Data::", hclog.Fmt("%+v", string(dataResource))) + var deviceInfo interface{} + err = json.Unmarshal(dataResource, &deviceInfo) + if err != nil { + return nil, err + } + return deviceInfo, nil +} + +// delete device from CM +func (p *BigipNextCM) DeleteDevice(deviceId string) error { + // deviceUrl := fmt.Sprintf("%s/%s", uriInventory, deviceId) + deviceUrl := fmt.Sprintf("%s/%s", uriDiscoverInstance, deviceId) + url := fmt.Sprintf("%s%s%s", p.Host, uriCMRoot, deviceUrl) + f5osLogger.Info("[DeleteDevice]", "Request path", hclog.Fmt("%+v", url)) + //{"save_backup":false} + var data = []byte(`{"save_backup":false}`) + respData, err := p.doCMRequest("DELETE", url, data) + // respData, err := p.DeleteCMRequest("DELETE", deviceUrl, data) + if err != nil { + return err + } + // {"_links":{"self":{"href":"/v1/deletion-tasks/02752890-5660-450c-ace9-b8e0a86a15ad"}},"path":"/v1/deletion-tasks/02752890-5660-450c-ace9-b8e0a86a15ad"} + respString := make(map[string]interface{}) + err = json.Unmarshal(respData, &respString) + if err != nil { + return err + } + f5osLogger.Info("[DeleteDevice]", "Task Path", hclog.Fmt("%+v", respString["path"].(string))) + //get task id from path + pathList := strings.Split(respString["path"].(string), "/") + taskId := pathList[len(pathList)-1] + f5osLogger.Info("[DeleteDevice]", "Task Id", hclog.Fmt("%+v", taskId)) + err = p.deleteTaskStatus(taskId) + if err != nil { + return err + } + f5osLogger.Info("[DeleteDevice]", "Data::", hclog.Fmt("%+v", string(respData))) + return nil +} + +// https://10.10.10.10/api/device/v1/deletion-tasks/02752890-5660-450c-ace9-b8e0a86a15ad +// verify device deletion task status +func (p *BigipNextCM) deleteTaskStatus(taskID string) error { + deviceUrl := fmt.Sprintf("%s/%s", "/device/v1/deletion-tasks", taskID) + url := fmt.Sprintf("%s%s%s", p.Host, uriCMRoot, deviceUrl) + f5osLogger.Info("[deleteTaskStatus]", "Request path", hclog.Fmt("%+v", url)) + timeout := 360 * time.Second + endtime := time.Now().Add(timeout) + respString := make(map[string]interface{}) + for time.Now().Before(endtime) { + respData, err := p.doCMRequest("GET", url, nil) + if err != nil { + return err + } + f5osLogger.Debug("[deleteTaskStatus]", "Data::", hclog.Fmt("%+v", string(respData))) + // {"_links":{"self":{"href":"/v1/deletion-tasks/642d5964-8cd9-4881-9086-1ed5ca682101"}},"address":"10.146.168.20","created":"2023-11-28T07:55:50.924918Z","device_id":"8d6c8c85-1738-4a34-b57b-d3644a2ecfcc","id":"642d5964-8cd9-4881-9086-1ed5ca682101","state":"factoryResetInstance","status":"running"} + err = json.Unmarshal(respData, &respString) + if err != nil { + return err + } + f5osLogger.Info("[deleteTaskStatus]", "Task Status", hclog.Fmt("%+v", respString["status"].(string))) + if respString["status"].(string) == "completed" { + return nil + } + if respString["status"].(string) == "failed" { + return fmt.Errorf("%s", respString) + } + time.Sleep(10 * time.Second) + } + return fmt.Errorf("%s", respString) +} + +func (p *BigipNextCM) PostDeviceProvider(config interface{}) (*DeviceProviderResponse, error) { + providerUrl := fmt.Sprintf("%s/vsphere", uriProviders) + if config.(*DeviceProviderReq).Type == "VELOS" || config.(*DeviceProviderReq).Type == "RSERIES" { + providerUrl = fmt.Sprintf("%s/f5os", uriProviders) + } + f5osLogger.Debug("[PostDeviceProvider]", "URI Path", providerUrl) + f5osLogger.Debug("[PostDeviceProvider]", "Config", hclog.Fmt("%+v", config)) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.PostCMRequest(providerUrl, body) + if err != nil { + if strings.Contains(err.Error(), "cert_fingerprint") { + config.(*DeviceProviderReq).Connection.CertFingerprint = strings.ReplaceAll(strings.Split(string(strings.Split(err.Error(), ",")[1]), ":")[2], "\"", "") + return p.PostDeviceProvider(config) + } + return nil, err + } + f5osLogger.Debug("[PostDeviceProvider]", "Resp::", hclog.Fmt("%+v", string(respData))) + var providerResp DeviceProviderResponse + err = json.Unmarshal(respData, &providerResp) + if err != nil { + return nil, err + } + return &providerResp, nil +} + +func (p *BigipNextCM) UpdateDeviceProvider(providerId string, config interface{}) (*DeviceProviderResponse, error) { + providerUrl := fmt.Sprintf("%s/vsphere", uriProviders) + if config.(*DeviceProviderReq).Type == "VELOS" || config.(*DeviceProviderReq).Type == "RSERIES" { + providerUrl = fmt.Sprintf("%s/f5os", uriProviders) + } + providerUrl = fmt.Sprintf("%s/%s", providerUrl, providerId) + f5osLogger.Debug("[UpdateDeviceProvider]", "URI Path", providerUrl) + f5osLogger.Debug("[UpdateDeviceProvider]", "Config", hclog.Fmt("%+v", config)) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.PutCMRequest(providerUrl, body) + if err != nil { + return nil, err + } + f5osLogger.Debug("[UpdateDeviceProvider]", "Resp::", hclog.Fmt("%+v", string(respData))) + var providerResp DeviceProviderResponse + err = json.Unmarshal(respData, &providerResp) + if err != nil { + return nil, err + } + return &providerResp, nil +} + +func (p *BigipNextCM) GetDeviceProvider(providerId, providerType string) (*DeviceProviderResponse, error) { + providerUrl := fmt.Sprintf("%s/vsphere", uriProviders) + if stringToUppercase(providerType) == "VELOS" || stringToUppercase(providerType) == "RSERIES" { + providerUrl = fmt.Sprintf("%s/f5os", uriProviders) + } + providerUrl = fmt.Sprintf("%s/%s", providerUrl, providerId) + f5osLogger.Debug("[GetDeviceProvider]", "URI Path", providerUrl) + respData, err := p.GetCMRequest(providerUrl) + if err != nil { + return nil, err + } + f5osLogger.Info("[GetDeviceProvider]", "\n--------Resp--------\n", hclog.Fmt("%+v", string(respData))) + var providerResp DeviceProviderResponse + err = json.Unmarshal(respData, &providerResp) + if err != nil { + return nil, err + } + return &providerResp, nil +} + +func (p *BigipNextCM) DeleteDeviceProvider(providerId, providerType string) ([]byte, error) { + providerUrl := fmt.Sprintf("%s/vsphere", uriProviders) + if stringToUppercase(providerType) == "VELOS" || stringToUppercase(providerType) == "RSERIES" { + providerUrl = fmt.Sprintf("%s/f5os", uriProviders) + } + providerUrl = fmt.Sprintf("%s/%s", providerUrl, providerId) + f5osLogger.Debug("[DeleteDeviceProvider]", "URI Path", providerUrl) + respData, err := p.DeleteCMRequest(providerUrl) + if err != nil { + return nil, err + } + f5osLogger.Info("[DeleteDeviceProvider]", "Data::", hclog.Fmt("%+v", string(respData))) + return respData, nil +} + +func (p *BigipNextCM) GetDeviceProviderIDByHostname(hostname string) (interface{}, error) { + providerUrl := fmt.Sprintf("%s?filter=name+eq+'%s'", uriProviders, hostname) + f5osLogger.Info("[GetDeviceProviderIDByHostname]", "URI Path", providerUrl) + respData, err := p.GetCMRequest(providerUrl) + if err != nil { + return nil, err + } + f5osLogger.Info("[GetDeviceProviderIDByHostname]", "provider query response:", hclog.Fmt("%+v", string(respData))) + var providerResp []interface{} + err = json.Unmarshal(respData, &providerResp) + if err != nil { + return nil, err + } + if len(providerResp) == 1 && providerResp[0].(map[string]interface{})["provider_name"].(string) == hostname { + return providerResp[0].(map[string]interface{})["provider_id"].(string), nil + } + return nil, fmt.Errorf("failed to get ID for provider: %+v", hostname) +} + +func (p *BigipNextCM) GetDeviceIdByHostname(deviceHostname string) (deviceId *string, err error) { + deviceUrl := fmt.Sprintf("%s?filter=hostname+eq+'%s'", uriInventory, deviceHostname) + f5osLogger.Info("[GetDeviceIdByHostname]", "URI Path", deviceUrl) + bigipNextDevice := &DeviceInventoryList{} + respData, err := p.GetCMRequest(deviceUrl) + if err != nil { + return nil, err + } + f5osLogger.Info("[GetDeviceIdByHostname]", "Resp BIG-IP Next:", hclog.Fmt("%+v", string(respData))) + err = json.Unmarshal(respData, &bigipNextDevice) + if err != nil { + return nil, err + } + + if bigipNextDevice.Count == 1 { + deviceId := bigipNextDevice.Embedded.Devices[0].Id + return &deviceId, nil + } + return nil, fmt.Errorf("the requested device:%s, was not found", deviceHostname) +} + +func (p *BigipNextCM) PostDeviceInstance(config *CMReqDeviceInstance, timeout int) ([]byte, error) { + uriInstances := "/device/v1/instances" + instanceUrl := fmt.Sprintf("%s", uriInstances) + f5osLogger.Debug("[PostDeviceInstance]", "URI Path", instanceUrl) + f5osLogger.Debug("[PostDeviceInstance]", "Config", hclog.Fmt("%+v", config)) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.PostCMRequest(instanceUrl, body) + if err != nil { + return nil, err + } + f5osLogger.Debug("[PostDeviceInstance]", "Data::", hclog.Fmt("%+v", string(respData))) + // {"_links":{"self":{"href":"/v1/instances/tasks/deacca61-3162-4672-aac8-2d6bd2b69438"}},"path":"/v1/instances/tasks/deacca61-3162-4672-aac8-2d6bd2b69438"} + respString := make(map[string]interface{}) + err = json.Unmarshal(respData, &respString) + if err != nil { + return nil, err + } + f5osLogger.Debug("[PostDeviceInstance]", "Task Path", hclog.Fmt("%+v", respString["path"].(string))) + // split path string to get task id + taskId := strings.Split(respString["path"].(string), "/") + f5osLogger.Info("[PostDeviceInstance]", "Task Id", hclog.Fmt("%+v", taskId[len(taskId)-1])) + // get task status + taskData, err := p.GetDeviceInstanceTaskStatus(taskId[len(taskId)-1], timeout) + if err != nil { + return nil, err + } + f5osLogger.Info("[PostDeviceInstance]", "Task Status", hclog.Fmt("%+v", taskData)) + return respData, nil +} + +// /v1/instances/tasks/deacca61-3162-4672-aac8-2d6bd2b69438 +// get device instance task status +func (p *BigipNextCM) GetDeviceInstanceTaskStatus(taskID string, timeOut int) (map[string]interface{}, error) { + // taskData := &DeviceInstanceTaskStatus{} + taskData := make(map[string]interface{}) + instanceUrl := fmt.Sprintf("%s%s", "/device/v1/instances/tasks/", taskID) + f5osLogger.Debug("[GetDeviceInstanceTaskStatus]", "URI Path", instanceUrl) + // var timeout time.Duration + timeout := time.Duration(timeOut) * time.Second + endtime := time.Now().Add(timeout) + for time.Now().Before(endtime) { + respData, err := p.GetCMRequest(instanceUrl) + if err != nil { + return nil, err + } + f5osLogger.Info("[GetDeviceInstanceTaskStatus]", "Task Status:\t", hclog.Fmt("%+v", string(respData))) + err = json.Unmarshal(respData, &taskData) + if err != nil { + return nil, err + } + if taskData["status"] == "completed" { + return taskData, nil + } + if taskData["status"] == "failed" { + return nil, fmt.Errorf("%s", taskData["failure_reason"]) + } + inVal := timeOut / 10 + time.Sleep(time.Duration(inVal) * time.Second) + } + return nil, fmt.Errorf("task Status is still in :%+v within timeout period of:%+v", taskData["status"], timeout) +} + +type LicenseReq struct { + DigitalAssetId string `json:"digitalAssetId,omitempty"` + JwtId string `json:"jwtId,omitempty"` +} + +// https://clouddocs.f5.com/api/v1/spaces/default/instances/license/activate +// Activate License Post Req +func (p *BigipNextCM) PostActivateLicense(config interface{}) (interface{}, error) { + uriLicenseActivate := fmt.Sprintf("%s%s", uriLicense, "/activate") + f5osLogger.Debug("[PostActivateLicense]", "URI Path", uriLicenseActivate) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.PostCMRequest(uriLicenseActivate, body) + if err != nil { + return nil, err + } + + // { + // "422b0cec-03b9-4499-a26e-c88f57869637": { + // "_links": { + // "self": { + // "href": "/license-task/41e49d68-d146-4e16-b286-7b57731fe14d" + // } + // }, + // "accepted": true, + // "deviceId": "422b0cec-03b9-4499-a26e-c88f57869637", + // "reason": "", + // "taskId": "41e49d68-d146-4e16-b286-7b57731fe14d" + // }, + // "bf89ae4b-c8f1-4c93-b47e-f2051513ad2f": { + // "_links": { + // "self": { + // "href": "/license-task/10786da5-a6a0-45fd-83d3-9db89a8f0a33" + // } + // }, + // "accepted": true, + // "deviceId": "bf89ae4b-c8f1-4c93-b47e-f2051513ad2f", + // "reason": "", + // "taskId": "10786da5-a6a0-45fd-83d3-9db89a8f0a33" + // } + // } + // get taskid + + respMap := make(map[string]interface{}) + err = json.Unmarshal(respData, &respMap) + if err != nil { + return nil, err + } + f5osLogger.Debug("[PostActivateLicense]", "Task Path", hclog.Fmt("%+v", respMap)) + // get task id from respMap for each device + var taskIds []string + for _, v := range respMap { + f5osLogger.Info("[PostActivateLicense]", "Task Id", hclog.Fmt("%+v", v.(map[string]interface{})["taskId"].(string))) + taskIds = append(taskIds, v.(map[string]interface{})["taskId"].(string)) + } + f5osLogger.Debug("[PostActivateLicense]", "taskIds:", hclog.Fmt("%+v", taskIds)) + lictskReq := &LicenseTaskReq{} + lictskReq.LicenseTaskIds = taskIds + return p.PostLicenseTaskStatus(lictskReq) + // return taskIds, nil +} + +// { +// "licenseTaskIds": [ +// "d290f1ee-6c54-4b01-90e6-d701748f0851", +// "d290f1ee-6c54-4b01-90e6-d701748f0852", +// "d290f1ee-6c54-4b01-90e6-d701748f0853" +// ] +// } + +type LicenseTaskReq struct { + LicenseTaskIds []string `json:"licenseTaskIds,omitempty"` +} + +// https://clouddocs.f5.com/api/v1/spaces/default/license/tasks +// Create POST call to get license task status +func (p *BigipNextCM) PostLicenseTaskStatus(config interface{}) (interface{}, error) { + uriLicenseTasks := "/v1/spaces/default/license/tasks" + f5osLogger.Debug("[PostLicenseTaskStatus]", "URI Path", uriLicenseTasks) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.PostCMRequest(uriLicenseTasks, body) + if err != nil { + return nil, err + } + + // { + // "3e45e2bd-4e01-4926-8794-67bf8ceb4f61": { + // "_links": { + // "self": { + // "href": "/license-task/3e45e2bd-4e01-4926-8794-67bf8ceb4f61" + // } + // }, + // "taskExecutionStatus": { + // "created": "2024-06-27T15:59:25.928845Z", + // "failureReason": "", + // "status": "completed", + // "subStatus": "TERMINATE_ACK_VERIFICATION_COMPLETE", + // "taskType": "deactivate" + // } + // }, + // "dfeb50ae-c664-4b76-ac29-540f5e5178ab": { + // "_links": { + // "self": { + // "href": "/license-task/dfeb50ae-c664-4b76-ac29-540f5e5178ab" + // } + // }, + // "taskExecutionStatus": { + // "created": "2024-06-27T15:59:25.914384Z", + // "failureReason": "", + // "status": "completed", + // "subStatus": "TERMINATE_ACK_VERIFICATION_COMPLETE", + // "taskType": "deactivate" + // } + // } + // } + + respMap := make(map[string]interface{}) + err = json.Unmarshal(respData, &respMap) + if err != nil { + return nil, err + } + f5osLogger.Debug("[PostLicenseTaskStatus]", "Task Path", hclog.Fmt("%+v", respMap)) + // verify taskExecutionStatus + count := 0 + for k, v := range respMap { + f5osLogger.Info("[PostLicenseTaskStatus]", "Task Id", hclog.Fmt("%+v", k)) + if v.(map[string]interface{})["taskExecutionStatus"].(map[string]interface{})["status"].(string) == "completed" { + count++ + } else if v.(map[string]interface{})["taskExecutionStatus"].(map[string]interface{})["status"].(string) == "failed" { + return nil, fmt.Errorf("%s", v.(map[string]interface{})["taskExecutionStatus"].(map[string]interface{})["failureReason"].(string)) + } else { + time.Sleep(30 * time.Second) + return p.PostLicenseTaskStatus(config) + } + } + if count == len(respMap) { + return respMap, nil + } + return respMap, nil +} + +// https://clouddocs.f5.com/api/v1/spaces/default/instances/license/license-info +// create POST call to get license info +func (p *BigipNextCM) PostLicenseInfo(config interface{}) (interface{}, error) { + uriLicenseInfo := fmt.Sprintf("%s%s", uriLicense, "/license-info") + // uriLicenseInfo := "/v1/spaces/default/instances/license/license-info" + f5osLogger.Debug("[PostLicenseInfo]", "URI Path", uriLicenseInfo) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.PostCMRequest(uriLicenseInfo, body) + if err != nil { + return nil, err + } + f5osLogger.Debug("[PostLicenseInfo]", "Data::", hclog.Fmt("%+v", string(respData))) + //conver to map + respMap := make(map[string]interface{}) + err = json.Unmarshal(respData, &respMap) + if err != nil { + return nil, err + } + return respMap, nil +} + +type LicenseDeactivaeReq struct { + DigitalAssetIds []string `json:"digitalAssetIds,omitempty"` +} + +// https://clouddocs.f5.com/api/v1/spaces/default/instances/license/deactivate +func (p *BigipNextCM) PostDeactivateLicense(config interface{}) (interface{}, error) { + uriLicenseDeactivate := fmt.Sprintf("%s%s", uriLicense, "/deactivate") + // uriLicenseDeactivate := "/v1/spaces/default/instances/license/deactivate" + f5osLogger.Debug("[PostDeactivateLicense]", "URI Path", uriLicenseDeactivate) + body, err := json.Marshal(config) + if err != nil { + return nil, err + } + respData, err := p.PostCMRequest(uriLicenseDeactivate, body) + if err != nil { + return nil, err + } + respMap := make(map[string]interface{}) + err = json.Unmarshal(respData, &respMap) + if err != nil { + return nil, err + } + f5osLogger.Debug("[PostDeactivateLicense]", "Task Path", hclog.Fmt("%+v", respMap)) + // get task id from respMap for each device + var taskIds []string + for _, v := range respMap { + f5osLogger.Info("[PostDeactivateLicense]", "Task Id", hclog.Fmt("%+v", v.(map[string]interface{})["taskId"].(string))) + taskIds = append(taskIds, v.(map[string]interface{})["taskId"].(string)) + } + lictskReq := &LicenseTaskReq{} + lictskReq.LicenseTaskIds = taskIds + return p.PostLicenseTaskStatus(lictskReq) + // f5osLogger.Debug("[PostDeactivateLicense]", "taskIds:", hclog.Fmt("%+v", taskIds)) + // return taskIds, nil +} + +// https://10.145.75.237/api/llm/license/a2064013-659d-4de0-8c22-773d21414885/status +// get device license status +func (p *BigipNextCM) GetDeviceLicenseStatus(deviceId *string) ([]byte, error) { + uriLicense := "/llm/license" + licenseUrl := fmt.Sprintf("%s/%s/status", uriLicense, *deviceId) + f5osLogger.Debug("[GetDeviceLicenseStatus]", "URI Path", licenseUrl) + respData, err := p.GetCMRequest(licenseUrl) + if err != nil { + return nil, err + } + f5osLogger.Debug("[GetDeviceLicenseStatus]", "Requested License Status:", hclog.Fmt("%+v", string(respData))) + return respData, nil +} + +func encodeUrl(urlName string) string { + // Encode the urlName + urlNameEncoded := url.QueryEscape(urlName) + return urlNameEncoded +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 842e979..a927e2f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -364,7 +364,7 @@ github.com/zclconf/go-cty/cty/function/stdlib github.com/zclconf/go-cty/cty/gocty github.com/zclconf/go-cty/cty/json github.com/zclconf/go-cty/cty/set -# gitswarm.f5net.com/terraform-providers/bigipnext v0.0.0-20240620103831-5476a6be942d +# gitswarm.f5net.com/terraform-providers/bigipnext v0.0.2 ## explicit; go 1.19 gitswarm.f5net.com/terraform-providers/bigipnext # golang.org/x/crypto v0.21.0