diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..b35b1dfce --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,37 @@ +--- +name: GoLang Build +on: + pull_request: + types: ['opened', 'synchronize'] + paths: + - '**.go' + - 'vendor/**' + - '.github/workflows/**' +jobs: + gobuild: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + # list whatever GO versions here you would like to support + golang: + - '1.19.*' +# - '1.18.*' +# - '1.17.*' +# - '1.16.*' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.golang }} + cache: true + - name: Get dependencies + run: | + go mod download + - name: Build + run: | + ls -ltr + pwd + go version + go build -v . + ls -ltr \ No newline at end of file diff --git a/.gitignore b/.gitignore index 024d5091a..a4725c97e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ website/node_modules *.test *.tf .terraform.lock.hcl +cover.out website/vendor keymap README.md diff --git a/README.md b/README.md index 1d790d2f8..b7425e1a9 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,15 @@ A [Terraform](terraform.io) provider for F5 BigIP LTM. -[![Build Status](https://travis-ci.org/f5devcentral/terraform-provider-bigip.svg?branch=master)](https://travis-ci.org/f5devcentral/terraform-provider-bigip) +![Build Status](https://github.com/F5Networks/terraform-provider-bigip/actions/workflows/golint.yaml/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/f5devcentral/terraform-provider-bigip)](https://goreportcard.com/report/github.com/f5devcentral/terraform-provider-bigip) [![license](https://img.shields.io/badge/license-Mozilla-red.svg?style=flat)](https://github.com/f5devcentral/terraform-provider-bigip/blob/master/LICENSE) -[![Gitter chat](https://badges.gitter.im/hashicorp-terraform/Lobby.png)](https://gitter.im/hashicorp-terraform/Lobby) +[![GitHub release(latest by date)](https://img.shields.io/github/v/release/F5Networks/terraform-provider-bigip)](https://github.com/F5Networks/terraform-provider-bigip/releases) - # Requirements -- [Terraform](https://www.terraform.io/downloads.html) 0.11.x / 0.12.x /0.13.x -- [Go](https://golang.org/doc/install) 1.16 (to build the provider plugin) +- [Terraform](https://www.terraform.io/downloads.html) > 0.12.x +- [Go](https://golang.org/doc/install) 1.19 (to build the provider plugin) # F5 BigIP LTM requirements @@ -24,12 +23,12 @@ A [Terraform](terraform.io) provider for F5 BigIP LTM. These BIG-IP versions are supported in these Terraform versions. -| BIG-IP version |Terraform 1.x | Terraform 0.13 | Terraform 0.12 | Terraform 0.11 | -|-----------------|---------------|-----------------|-----------------|-----------------| -| BIG-IP 17.x | X | X | X | X | -| BIG-IP 16.x | X | X | X | X | -| BIG-IP 15.x | X | X | X | X | -| BIG-IP 14.x | X | X | X | X | +| BIG-IP version | Terraform 1.x | Terraform 0.13 | Terraform 0.12 | +|-----------------|---------------|-----------------|-----------------| +| BIG-IP 17.x | X | X | X | +| BIG-IP 16.x | X | X | X | +| BIG-IP 15.x | X | X | X | +| BIG-IP 14.x | X | X | X | # Documentation diff --git a/bigip/provider.go b/bigip/provider.go index e148db91d..8e5f21ee5 100644 --- a/bigip/provider.go +++ b/bigip/provider.go @@ -153,6 +153,7 @@ func Provider() *schema.Provider { "bigip_fast_udp_app": resourceBigipFastUdpApp(), "bigip_ssl_certificate": resourceBigipSslCertificate(), "bigip_ssl_key": resourceBigipSslKey(), + "bigip_ssl_key_cert": resourceBigipSSLKeyCert(), "bigip_command": resourceBigipCommand(), "bigip_common_license_manage_bigiq": resourceBigiqLicenseManage(), "bigip_bigiq_as3": resourceBigiqAs3(), diff --git a/bigip/resource_bigip_as3.go b/bigip/resource_bigip_as3.go index 8bcacc816..15c180589 100644 --- a/bigip/resource_bigip_as3.go +++ b/bigip/resource_bigip_as3.go @@ -23,7 +23,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" ) -var x = 0 +// var x = 0 var m sync.Mutex var createdTenants string @@ -167,12 +167,12 @@ func resourceBigipAs3() *schema.Resource { func resourceBigipAs3Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*bigip.BigIP) - as3Json := d.Get("as3_json").(string) m.Lock() defer m.Unlock() - log.Printf("[INFO] Creating As3 config") + as3Json := d.Get("as3_json").(string) tenantFilter := d.Get("tenant_filter").(string) tenantList, _, applicationList := client.GetTenantList(as3Json) + log.Printf("[INFO] Creating As3 config for tenants:%+v", tenantList) tenantCount := strings.Split(tenantList, ",") if tenantFilter != "" { log.Printf("[DEBUG] tenantFilter:%+v", tenantFilter) @@ -227,8 +227,6 @@ func resourceBigipAs3Create(ctx context.Context, d *schema.ResourceData, meta in d.SetId("Common") } createdTenants = d.Get("tenant_list").(string) - x++ - log.Printf("[TRACE] %+v", x) return resourceBigipAs3Read(ctx, d, meta) } func resourceBigipAs3Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -286,11 +284,12 @@ func resourceBigipAs3Read(ctx context.Context, d *schema.ResourceData, meta inte func resourceBigipAs3Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*bigip.BigIP) - as3Json := d.Get("as3_json").(string) m.Lock() defer m.Unlock() + as3Json := d.Get("as3_json").(string) log.Printf("[INFO] Updating As3 Config :%s", as3Json) tenantList, _, _ := client.GetTenantList(as3Json) + log.Printf("[INFO] Updating As3 Config for tenants:%s", tenantList) oldTenantList := d.Get("tenant_list").(string) tenantFilter := d.Get("tenant_filter").(string) if tenantFilter == "" { @@ -331,7 +330,6 @@ func resourceBigipAs3Update(ctx context.Context, d *schema.ResourceData, meta in } createdTenants = d.Get("tenant_list").(string) _ = d.Set("task_id", taskID) - x++ return resourceBigipAs3Read(ctx, d, meta) } @@ -339,7 +337,6 @@ func resourceBigipAs3Delete(ctx context.Context, d *schema.ResourceData, meta in client := meta.(*bigip.BigIP) m.Lock() defer m.Unlock() - log.Printf("[INFO] Deleting As3 config") var name string var tList string @@ -352,6 +349,7 @@ func resourceBigipAs3Delete(ctx context.Context, d *schema.ResourceData, meta in } else { name = d.Id() } + log.Printf("[INFO] Deleting As3 config for tenants:%+v", name) err, failedTenants := client.DeleteAs3Bigip(name) if err != nil { log.Printf("[ERROR] Unable to DeleteContext: %v :", err) @@ -361,7 +359,6 @@ func resourceBigipAs3Delete(ctx context.Context, d *schema.ResourceData, meta in _ = d.Set("tenant_list", name) return resourceBigipAs3Read(ctx, d, meta) } - x++ d.SetId("") return nil } diff --git a/bigip/resource_bigip_as3_test.go b/bigip/resource_bigip_as3_test.go index b42ad166a..b5145abca 100644 --- a/bigip/resource_bigip_as3_test.go +++ b/bigip/resource_bigip_as3_test.go @@ -128,7 +128,8 @@ func TestAccBigipAs3_create_PartialSuccess(t *testing.T) { CheckDestroy: testCheckAs3Destroy, Steps: []resource.TestStep{ { - Config: TestAs3Resource2, + Config: TestAs3Resource2, + ExpectError: regexp.MustCompile("as3 config post error response"), Check: resource.ComposeTestCheckFunc( testCheckAs3Exists("Sample_03", true), testCheckAs3Exists("Sample_04", false), @@ -206,6 +207,25 @@ func TestAccBigipAs3_update_deleteTenant(t *testing.T) { }) } +func TestAccBigipAs3ResourceTC20(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAcctPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testCheckAs3Destroy, + Steps: []resource.TestStep{ + { + Config: loadFixtureString("../examples/as3/main.tf"), + ExpectError: regexp.MustCompile("posting as3 config failed for tenants"), + Check: resource.ComposeTestCheckFunc( + testCheckAs3Exists("Sample_http_02", false), + ), + }, + }, + }) +} + func TestAccBigipAs3_update_config(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { diff --git a/bigip/resource_bigip_awaf_policy.go b/bigip/resource_bigip_awaf_policy.go index e7fe9775d..063fbf6f7 100644 --- a/bigip/resource_bigip_awaf_policy.go +++ b/bigip/resource_bigip_awaf_policy.go @@ -71,7 +71,6 @@ func resourceBigipAwafPolicy() *schema.Resource { "application_language": { Type: schema.TypeString, Optional: true, - Default: "utf-8", Description: "The character encoding for the web application. The character encoding determines how the policy processes the character sets. The default is Auto detect", }, "case_insensitive": { @@ -467,7 +466,9 @@ func resourceBigipAwafPolicyRead(ctx context.Context, d *schema.ResourceData, me } _ = d.Set("policy_id", wafpolicy.ID) _ = d.Set("type", policyJson.Policy.Type) - _ = d.Set("application_language", policyJson.Policy.ApplicationLanguage) + if _, ok := d.GetOk("application_language"); ok { + _ = d.Set("application_language", policyJson.Policy.ApplicationLanguage) + } if _, ok := d.GetOk("enforcement_mode"); ok { _ = d.Set("enforcement_mode", policyJson.Policy.EnforcementMode) } @@ -538,11 +539,16 @@ func getpolicyConfig(d *schema.ResourceData) (string, error) { if partition != "Common" { fullPath = fmt.Sprintf("/%s/%s", partition, name) } + var appLang1 string + appLang1 = "auto-detect" + if val, ok := d.GetOk("application_language"); ok { + appLang1 = val.(string) + } policyWaf := bigip.WafPolicy{ Name: name, Partition: partition, FullPath: fullPath, - ApplicationLanguage: d.Get("application_language").(string), + ApplicationLanguage: appLang1, } policyWaf.CaseInsensitive = d.Get("case_insensitive").(bool) policyWaf.EnablePassiveMode = d.Get("enable_passivemode").(bool) @@ -708,9 +714,12 @@ func getpolicyConfig(d *schema.ResourceData) (string, error) { if policyWaf.Template.Name != "" && polJsn1.Policy.(map[string]interface{})["template"] != policyWaf.Template { polJsn1.Policy.(map[string]interface{})["template"] = policyWaf.Template } - if policyWaf.ApplicationLanguage != "" { - polJsn1.Policy.(map[string]interface{})["applicationLanguage"] = policyWaf.ApplicationLanguage + if appLang, ok := d.GetOk("application_language"); ok { + polJsn1.Policy.(map[string]interface{})["applicationLanguage"] = appLang } + // if policyWaf.ApplicationLanguage != "" { + // polJsn1.Policy.(map[string]interface{})["applicationLanguage"] = policyWaf.ApplicationLanguage + // } urlList := make([]interface{}, len(policyWaf.Urls)) for i, v := range policyWaf.Urls { urlList[i] = v diff --git a/bigip/resource_bigip_do.go b/bigip/resource_bigip_do.go index f48eaa10f..6e4715bd5 100644 --- a/bigip/resource_bigip_do.go +++ b/bigip/resource_bigip_do.go @@ -132,7 +132,11 @@ func resourceBigipDoCreate(ctx context.Context, d *schema.ResourceData, meta int if err != nil { return diag.FromErr(fmt.Errorf("error while creating http request with DO json:%v", err)) } - req.SetBasicAuth(clientBigip.User, clientBigip.Password) + if clientBigip.Token != "" { + req.Header.Set("X-F5-Auth-Token", clientBigip.Token) + } else { + req.SetBasicAuth(clientBigip.User, clientBigip.Password) + } req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") @@ -180,10 +184,13 @@ func resourceBigipDoCreate(ctx context.Context, d *schema.ResourceData, meta int log.Printf("[DEBUG]Value of Timeout counter in seconds :%v", math.Ceil(time.Since(start).Seconds())) url := clientBigip.Host + "/mgmt/shared/declarative-onboarding/task/" + respID req, _ := http.NewRequest("GET", url, nil) - req.SetBasicAuth(clientBigip.User, clientBigip.Password) + if clientBigip.Token != "" { + req.Header.Set("X-F5-Auth-Token", clientBigip.Token) + } else { + req.SetBasicAuth(clientBigip.User, clientBigip.Password) + } req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") - taskResp, err := client.Do(req) if taskResp == nil { log.Printf("[DEBUG]taskResp of DO is empty,but continue the loop until timeout \n") @@ -240,7 +247,11 @@ func resourceBigipDoCreate(ctx context.Context, d *schema.ResourceData, meta int log.Printf("[DEBUG] Didn't get successful response within timeout") url := clientBigip.Host + "/mgmt/shared/declarative-onboarding/task/" + respID req, _ := http.NewRequest("GET", url, nil) - req.SetBasicAuth(clientBigip.User, clientBigip.Password) + if clientBigip.Token != "" { + req.Header.Set("X-F5-Auth-Token", clientBigip.Token) + } else { + req.SetBasicAuth(clientBigip.User, clientBigip.Password) + } req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") taskResp, err := client.Do(req) @@ -292,10 +303,13 @@ func resourceBigipDoRead(ctx context.Context, d *schema.ResourceData, meta inter if err != nil { return diag.FromErr(fmt.Errorf("error while creating http request for reading Do config:%v", err)) } - req.SetBasicAuth(clientBigip.User, clientBigip.Password) + if clientBigip.Token != "" { + req.Header.Set("X-F5-Auth-Token", clientBigip.Token) + } else { + req.SetBasicAuth(clientBigip.User, clientBigip.Password) + } req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") - resp, err := client.Do(req) defer func() { @@ -353,10 +367,13 @@ func resourceBigipDoUpdate(ctx context.Context, d *schema.ResourceData, meta int if err != nil { return diag.FromErr(fmt.Errorf("error while creating http request with DO json:%v ", err)) } - req.SetBasicAuth(clientBigip.User, clientBigip.Password) + if clientBigip.Token != "" { + req.Header.Set("X-F5-Auth-Token", clientBigip.Token) + } else { + req.SetBasicAuth(clientBigip.User, clientBigip.Password) + } req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") - resp, err := client.Do(req) defer func() { @@ -398,10 +415,13 @@ func resourceBigipDoUpdate(ctx context.Context, d *schema.ResourceData, meta int log.Printf("[DEBUG]Value of loop counter :%d", i) url := clientBigip.Host + "/mgmt/shared/declarative-onboarding/task/" + respID req, _ := http.NewRequest("GET", url, nil) - req.SetBasicAuth(clientBigip.User, clientBigip.Password) + if clientBigip.Token != "" { + req.Header.Set("X-F5-Auth-Token", clientBigip.Token) + } else { + req.SetBasicAuth(clientBigip.User, clientBigip.Password) + } req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") - taskResp, err := client.Do(req) defer func() { @@ -458,7 +478,11 @@ func resourceBigipDoUpdate(ctx context.Context, d *schema.ResourceData, meta int log.Printf("[DEBUG] Didn't get successful response within timeout") url := clientBigip.Host + "/mgmt/shared/declarative-onboarding/task/" + respID req, _ := http.NewRequest("GET", url, nil) - req.SetBasicAuth(clientBigip.User, clientBigip.Password) + if clientBigip.Token != "" { + req.Header.Set("X-F5-Auth-Token", clientBigip.Token) + } else { + req.SetBasicAuth(clientBigip.User, clientBigip.Password) + } req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") taskResp, err := client.Do(req) diff --git a/bigip/resource_bigip_do_test.go b/bigip/resource_bigip_do_test.go new file mode 100644 index 000000000..25dea83df --- /dev/null +++ b/bigip/resource_bigip_do_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2019 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 bigip + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccBigipDeclarativeOnboardTCs(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAcctPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: loadFixtureString("../examples/bigip_onboard.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchOutput("do_json", regexp.MustCompile("ecosyshyd-bigip02.com")), + ), + }, + { + Config: loadFixtureString("../examples/bigip_onboard_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchOutput("do_json", regexp.MustCompile("ecosyshyd-bigip03.com")), + ), + }, + }, + }) +} diff --git a/bigip/resource_bigip_ltm_policy.go b/bigip/resource_bigip_ltm_policy.go index 3c8af386a..6e4c8daab 100644 --- a/bigip/resource_bigip_ltm_policy.go +++ b/bigip/resource_bigip_ltm_policy.go @@ -46,6 +46,11 @@ func resourceBigipLtmPolicy() *schema.Resource { ForceNew: true, ValidateFunc: validateF5NameWithDirectory, }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies descriptive text that identifies the ltm policy.", + }, "published_copy": { Type: schema.TypeString, Optional: true, @@ -1182,35 +1187,6 @@ func resourceBigipLtmPolicyRead(ctx context.Context, d *schema.ResourceData, met return policyToData(p, d) } -// func resourceBigipLtmPolicyExists(d *schema.ResourceData, meta interface{}) (bool, error) { -// client := meta.(*bigip.BigIP) -// -// name := d.Id() -// polStr := strings.Split(name, "/") -// -// re := regexp.MustCompile("/([a-zA-z0-9? ,_-]+)/([a-zA-z0-9? ,._-]+)") -// match := re.FindStringSubmatch(name) -// if match == nil { -// return false, fmt.Errorf("Policy name failed to match the regex, and should be of format /partition/policy_name") -// } -// partition := strings.Join(polStr[:len(polStr)-1], "/") -// policyName := polStr[len(polStr)-1] -// -// log.Println("[INFO] Fetching policy " + policyName) -// p, err := client.GetPolicy(policyName, partition) -// -// if err != nil { -// log.Printf("[ERROR] Unable to Retrieve Policy (%s) (%v) ", name, err) -// return false, err -// } -// if p == nil { -// log.Printf("[WARN] Policy (%s) not found, removing from state", d.Id()) -// d.SetId("") -// return false, nil -// } -// return true, nil -//} - func resourceBigipLtmPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*bigip.BigIP) name := d.Id() @@ -1300,6 +1276,7 @@ func dataToPolicy(name string, d *schema.ResourceData) bigip.Policy { result := strings.Join(values, "") p.Name = result p.Strategy = d.Get("strategy").(string) + p.Description = d.Get("description").(string) p.Controls = setToStringSlice(d.Get("controls").(*schema.Set)) p.Requires = setToStringSlice(d.Get("requires").(*schema.Set)) @@ -1396,6 +1373,10 @@ func policyToData(p *bigip.Policy, d *schema.ResourceData) diag.Diagnostics { return diag.FromErr(fmt.Errorf("[DEBUG] Error saving Requires state for Policy (%s): %s", d.Id(), err)) } + if val, ok := d.GetOk("description"); ok { + p.Description = val.(string) + } + _ = d.Set("name", p.FullPath) if len(p.Rules) > 0 { diff --git a/bigip/resource_bigip_ltm_policy_test.go b/bigip/resource_bigip_ltm_policy_test.go index 519c43128..65a51fa99 100644 --- a/bigip/resource_bigip_ltm_policy_test.go +++ b/bigip/resource_bigip_ltm_policy_test.go @@ -514,10 +514,13 @@ func TestAccBigipLtmPolicyIssue794TestCases(t *testing.T) { testCheckPolicyExists("/Common/policy-issue-591"), testCheckPolicyExists("/Common/policy_issue794_tc1"), testCheckPolicyExists("/Common/policy_issue794_tc2"), + testCheckPolicyExists("/Common/policy_issue_838_tc1"), resource.TestCheckResourceAttr("bigip_ltm_policy.policy_issue794_tc1", "strategy", "first-match"), resource.TestCheckResourceAttr("bigip_ltm_policy.policy_issue794_tc1", "name", "/Common/policy_issue794_tc1"), resource.TestCheckResourceAttr("bigip_ltm_policy.policy_issue794_tc2", "strategy", "first-match"), resource.TestCheckResourceAttr("bigip_ltm_policy.policy_issue794_tc2", "name", "/Common/policy_issue794_tc2"), + resource.TestCheckResourceAttr("bigip_ltm_policy.policy_issue_838_tc1", "name", "/Common/policy_issue_838_tc1"), + resource.TestCheckResourceAttr("bigip_ltm_policy.policy_issue_838_tc1", "description", "policy_issue_838_tc1 description"), ), }, }, diff --git a/bigip/resource_bigip_net_selfip.go b/bigip/resource_bigip_net_selfip.go index 09a4867c7..3e811ca10 100644 --- a/bigip/resource_bigip_net_selfip.go +++ b/bigip/resource_bigip_net_selfip.go @@ -119,8 +119,11 @@ func resourceBigipNetSelfIPRead(ctx context.Context, d *schema.ResourceData, met // Extract Traffic Group name from the full path (ignoring /Common/ prefix) regex := regexp.MustCompile(`\/Common\/(.+)`) + _ = d.Set("traffic_group", selfIP.TrafficGroup) trafficGroup := regex.FindStringSubmatch(selfIP.TrafficGroup) - _ = d.Set("traffic_group", trafficGroup[1]) + if len(trafficGroup) > 0 { + _ = d.Set("traffic_group", trafficGroup[1]) + } if selfIP.AllowService == nil { _ = d.Set("port_lockdown", []string{"none"}) } else { diff --git a/bigip/resource_bigip_ssl_key_cert.go b/bigip/resource_bigip_ssl_key_cert.go new file mode 100644 index 000000000..51f35730d --- /dev/null +++ b/bigip/resource_bigip_ssl_key_cert.go @@ -0,0 +1,221 @@ +package bigip + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + + "github.com/f5devcentral/go-bigip" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceBigipSSLKeyCert() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceBigipSSLKeyCertCreate, + ReadContext: resourceBigipSSLKeyCertRead, + UpdateContext: resourceBigipSSLKeyCertUpdate, + DeleteContext: resourceBigipSSLKeyCertDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "key_name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the key.", + }, + "key_content": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + Description: "The content of the key.", + }, + "key_full_path": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Full Path Name of ssl key", + }, + "cert_name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the cert.", + ForceNew: true, + }, + "cert_content": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + Description: "The content of the cert.", + }, + "cert_full_path": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Full Path Name of ssl certificate", + }, + "passphrase": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: "Passphrase on the key.", + }, + "partition": { + Type: schema.TypeString, + Optional: true, + Default: "Common", + Description: "Partition on the ssl certificate and key.", + ValidateFunc: validatePartitionName, + }, + }, + } +} + +func resourceBigipSSLKeyCertCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*bigip.BigIP) + + keyName := d.Get("key_name").(string) + keyPath := d.Get("key_content").(string) + partition := d.Get("partition").(string) + passphrase := d.Get("passphrase").(string) + certName := d.Get("cert_name").(string) + certPath := d.Get("cert_content").(string) + + sourcePath, err := client.UploadKey(keyName, keyPath) + if err != nil { + return diag.FromErr(fmt.Errorf("error while uploading the ssl key: %v", err)) + } + + keyCfg := bigip.Key{ + Name: keyName, + SourcePath: sourcePath, + Partition: partition, + Passphrase: passphrase, + } + + err = client.AddKey(&keyCfg) + if err != nil { + return diag.FromErr(fmt.Errorf("error while adding the ssl key: %v", err)) + } + err = client.UploadCertificate(certName, certPath, partition) + if err != nil { + return diag.FromErr(fmt.Errorf("error while uploading the ssl cert: %v", err)) + } + + id := keyName + "_" + certName + d.SetId(id) + return resourceBigipSSLKeyCertRead(ctx, d, meta) +} + +func resourceBigipSSLKeyCertRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*bigip.BigIP) + partition := d.Get("partition").(string) + + keyName := fqdn(partition, d.Get("key_name").(string)) + certName := fqdn(partition, d.Get("cert_name").(string)) + + key, err := client.GetKey(keyName) + if err != nil { + diag.FromErr(err) + } + if key == nil { + return diag.Errorf("reading ssl key failed with key: %v", key) + } + + certificate, err := client.GetCertificate(certName) + if err != nil { + return diag.FromErr(err) + } + if certificate == nil { + return diag.Errorf("reading certificate failed :%+v", certificate) + } + + d.Set("key_name", key.Name) + d.Set("key_full_path", key.FullPath) + d.Set("cert_name", certificate.Name) + d.Set("cert_full_path", certificate.FullPath) + d.Set("partition", key.Partition) + + return nil +} + +func resourceBigipSSLKeyCertUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*bigip.BigIP) + + keyName := d.Get("key_name").(string) + keyPath := d.Get("key_content").(string) + partition := d.Get("partition").(string) + passphrase := d.Get("passphrase").(string) + certName := d.Get("cert_name").(string) + certPath := d.Get("cert_content").(string) + + sourcePath, err := client.UploadKey(keyName, keyPath) + if err != nil { + return diag.FromErr(fmt.Errorf("error while trying to upload ssl key (%s): %s", keyName, err)) + } + + keyCfg := bigip.Key{ + Name: keyName, + SourcePath: sourcePath, + Partition: partition, + Passphrase: passphrase, + } + + keyFullPath := fmt.Sprintf("/%s/%s", partition, keyName) + err = client.ModifyKey(keyFullPath, &keyCfg) + if err != nil { + return diag.FromErr(fmt.Errorf("error while trying to modify the ssl key (%s): %s", keyFullPath, err)) + } + + err = client.UpdateCertificate(certName, certPath, partition) + if err != nil { + return diag.FromErr(fmt.Errorf("error while updating the ssl certificate (%s): %s", certName, err)) + } + + return resourceBigipSSLKeyCertRead(ctx, d, meta) +} + +func resourceBigipSSLKeyCertDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*bigip.BigIP) + log.Println("[INFO] Deleting SSL Key and Certificate") + keyName := d.Get("key_name").(string) + partition := d.Get("partition").(string) + certName := d.Get("cert_name").(string) + + log.Printf("[INFO] Deleting SSL Key %s and Certificate %s", keyName, certName) + + keyFullPath := "/" + partition + "/" + keyName + certFullPath := "/" + partition + "/" + certName + + err := client.DeleteKey(keyFullPath) + if err != nil { + log.Printf("[ERROR] unable to delete the ssl key (%s) (%v) ", keyFullPath, err) + } + + err = client.DeleteCertificate(certFullPath) + if err != nil { + log.Printf("[ERROR] unable to delete the ssl certificate (%s) (%v) ", certFullPath, err) + } + + d.SetId("") + return nil +} + +func fqdn(partition, name string) string { + if partition == "" { + if !strings.HasPrefix(name, "/") { + err := errors.New("the key name must be in full_path format when partition is not specified") + fmt.Print(err) + } + } else { + if !strings.HasPrefix(name, "/") { + name = "/" + partition + "/" + name + } + } + + return name +} diff --git a/bigip/resource_bigip_ssl_key_cert_test.go b/bigip/resource_bigip_ssl_key_cert_test.go new file mode 100644 index 000000000..2b2a763b0 --- /dev/null +++ b/bigip/resource_bigip_ssl_key_cert_test.go @@ -0,0 +1,47 @@ +package bigip + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +var testResourceSSLKeyCert = ` +resource "bigip_ssl_key_cert" "testkeycert" { + partition = "Common" + key_name = "ssl-test-key" + key_content = "${file("` + folder + `/../examples/serverkey.key")}" + cert_name = "ssl-test-cert" + cert_content = "${file("` + folder + `/../examples/servercert.crt")}" +} +` + +func TestAccBigipSSLCertKeyCreate(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAcctPreCheck(t) + }, + Providers: testAccProviders, + // CheckDestroy: + Steps: []resource.TestStep{ + { + Config: testResourceSSLKeyCert, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("bigip_ssl_key_cert.testkeycert", "key_name", "ssl-test-key"), + resource.TestCheckResourceAttr("bigip_ssl_key_cert.testkeycert", "cert_name", "ssl-test-cert"), + resource.TestCheckResourceAttr("bigip_ssl_key_cert.testkeycert", "partition", "Common"), + ), + Destroy: false, + }, + { + Config: testResourceSSLKeyCert, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("bigip_ssl_key_cert.testkeycert", "key_name", "ssl-test-key"), + resource.TestCheckResourceAttr("bigip_ssl_key_cert.testkeycert", "cert_name", "ssl-test-cert"), + resource.TestCheckResourceAttr("bigip_ssl_key_cert.testkeycert", "partition", "Common"), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) +} diff --git a/bigip/resource_bigip_sys_iapp.go b/bigip/resource_bigip_sys_iapp.go index 40f0a30aa..1a408d443 100644 --- a/bigip/resource_bigip_sys_iapp.go +++ b/bigip/resource_bigip_sys_iapp.go @@ -243,10 +243,11 @@ func resourceBigipSysIappRead(ctx context.Context, d *schema.ResourceData, meta client := meta.(*bigip.BigIP) name := d.Id() + partition := d.Get("partition").(string) log.Println("[INFO] Reading Iapp " + name) - p, err := client.Iapp(name) + p, err := client.Iapp(name, partition) log.Printf("[INFO] Iapp Info:%+v", p) if err != nil { log.Printf("[ERROR] Unable to Retrieve Iapp (%s) (%v)", name, err) @@ -274,7 +275,8 @@ func resourceBigipSysIappRead(ctx context.Context, d *schema.ResourceData, meta func resourceBigipSysIappDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*bigip.BigIP) name := d.Id() - err := client.DeleteIapp(name) + partition := d.Get("partition").(string) + err := client.DeleteIapp(name, partition) if err != nil { log.Printf("[ERROR] Unable to Delete Iapp (%s) (%v)", name, err) return diag.FromErr(err) diff --git a/bigip/resource_bigip_sys_iapp_test.go b/bigip/resource_bigip_sys_iapp_test.go index 0bca2008c..a1404dee0 100644 --- a/bigip/resource_bigip_sys_iapp_test.go +++ b/bigip/resource_bigip_sys_iapp_test.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -var TestIappName = "/" + TestPartition + "/test-iapp" +var TestIappName = "test-iapp" var TestIappResource = ` resource "bigip_sys_iapp" "test-iapp" { @@ -83,7 +83,7 @@ var TestIappResource = ` "templateReference": { "link": "https://localhost/mgmt/tm/sys/application/template/~Common~f5.http?ver=13.0.0" }, - + "variables": [ { "encrypted": "no", @@ -156,7 +156,7 @@ func TestAccBigipSysIapp_create(t *testing.T) { { Config: TestIappResource, Check: resource.ComposeTestCheckFunc( - testCheckIappExists(TestIappName), + testCheckIappExists(TestIappName, TestPartition), ), }, }, @@ -174,7 +174,7 @@ func TestAccBigipSysIapp_import(t *testing.T) { { Config: TestIappResource, Check: resource.ComposeTestCheckFunc( - testCheckIappExists(TestIappName), + testCheckIappExists(TestIappName, TestPartition), ), ResourceName: TestIappName, ImportState: false, @@ -184,11 +184,11 @@ func TestAccBigipSysIapp_import(t *testing.T) { }) } -func testCheckIappExists(name string) resource.TestCheckFunc { +func testCheckIappExists(name, partition string) resource.TestCheckFunc { return func(s *terraform.State) error { client := testAccProvider.Meta().(*bigip.BigIP) - jsonfile, err := client.Iapp(name) + jsonfile, err := client.Iapp(name, partition) log.Println(" I am here in Exists !!!!!!!!!!!!", name) if err != nil { return fmt.Errorf("Error while fetching iapp: %v", err) @@ -216,10 +216,11 @@ func testCheckIappDestroyed(s *terraform.State) error { } name := rs.Primary.ID + partition := rs.Primary.Attributes["partition"] log.Println(" I am in Destroy function currently +++++++++++++++++++++++++++ ", name) // Join three strings into one. - jsonfile, err := client.Iapp(name) + jsonfile, err := client.Iapp(name, partition) if err != nil { return nil diff --git a/bigip/version.go b/bigip/version.go index 8ff3dac9e..bf532b07c 100644 --- a/bigip/version.go +++ b/bigip/version.go @@ -1,4 +1,4 @@ package bigip // ProviderVersion is set at build-time in the release process -var ProviderVersion = "1.18.1" +var ProviderVersion = "1.19.0" diff --git a/docs/resources/bigip_ltm_policy.md b/docs/resources/bigip_ltm_policy.md index 2dbc488d0..11c71733f 100644 --- a/docs/resources/bigip_ltm_policy.md +++ b/docs/resources/bigip_ltm_policy.md @@ -45,6 +45,8 @@ resource "bigip_ltm_policy" "test-policy" { * `strategy` - (Optional) Specifies the match strategy +* `description` - (Optional) Specifies descriptive text that identifies the ltm policy. + * `requires` - (Optional) Specifies the protocol * `published_copy` - (Optional) If you want to publish the policy else it will be deployed in Drafts mode. diff --git a/docs/resources/bigip_ssl_key_cert.md b/docs/resources/bigip_ssl_key_cert.md new file mode 100644 index 000000000..570cca38e --- /dev/null +++ b/docs/resources/bigip_ssl_key_cert.md @@ -0,0 +1,55 @@ +--- +layout: "bigip" +page_title: "BIG-IP: bigip_ssl_key_cert" +subcategory: "System" +description: |- + Provides details about bigip_ssl_key_cert resource +--- + +# bigip_ssl_key_cert + +`bigip_ssl_key_cert` This resource will import SSL certificate and key on BIG-IP LTM. +The certificate and the key can be imported from files on the local disk, in PEM format + + +## Example Usage + + +```hcl + +resource "bigip_ssl_key_cert" "testkeycert" { + partition = "Common" + key_name = "ssl-test-key" + key_content = file("key.pem") + cert_name = "ssl-test-cert" + cert_content = file("certificate.pem") +} + +``` + +## Argument Reference + + +* `key_name`- (Required,type `string`) Name of the SSL key to be Imported on to BIGIP. + +* `key_content` - (Required) Content of SSL key on Local Disk,path of SSL key will be provided to terraform `file` function. + +* `cert_name`- (Required,type `string`) Name of the SSL certificate to be Imported on to BIGIP. + +* `cert_content` - (Required) Content of certificate on Local Disk,path of SSL certificate will be provided to terraform `file` function. + +* `partition` - (Optional,type `string`) Partition on to SSL certificate and key to be imported. + +* `passphrase` - (Optional,type `string`) Passphrase on the SSL key. + + + +## Attribute Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - identifier of the resource. + +* `key_full_path` - full path of the SSL key on the BIGIP. + +* `cert_full_path` - full path of the SSL certificate on the BIGIP. diff --git a/docs/resources/bigip_sys_iapp.md b/docs/resources/bigip_sys_iapp.md index 8375c6ea4..ada0e98a0 100644 --- a/docs/resources/bigip_sys_iapp.md +++ b/docs/resources/bigip_sys_iapp.md @@ -8,7 +8,7 @@ description: |- # bigip\_sys\_iapp -`bigip_sys_iapp` resource helps you to deploy Application Services template that can be used to automate and orchestrate Layer 4-7 applications service deployments using F5 Network. +`bigip_sys_iapp` resource helps you to deploy Application Services template that can be used to automate and orchestrate Layer 4-7 applications service deployments using F5 Network. ## Example Usage @@ -30,7 +30,7 @@ resource "bigip_sys_iapp" "simplehttp" { ## Example Usage of Json file ```json -{ +{ "fullPath":"/Common/simplehttp.app/simplehttp", "generation":222, "inheritedDevicegroup":"true", @@ -41,46 +41,46 @@ resource "bigip_sys_iapp" "simplehttp" { "selfLink":"https://localhost/mgmt/tm/sys/application/service/~Common~simplehttp.app~simplehttp?ver=13.0.0", "strictUpdates":"enabled", "subPath":"simplehttp.app", -"tables":[ - { +"tables":[ + { "name":"basic__snatpool_members" }, - { + { "name":"net__snatpool_members" }, - { + { "name":"optimizations__hosts" }, - { - "columnNames":[ + { + "columnNames":[ "name" ], "name":"pool__hosts", - "rows":[ - { - "row":[ + "rows":[ + { + "row":[ "f5.cisco.com" ] } ] }, - { - "columnNames":[ + { + "columnNames":[ "addr", "port", "connection_limit" ], "name":"pool__members", - "rows":[ - { - "row":[ + "rows":[ + { + "row":[ "10.0.2.167", "80", "0" ] }, - { - "row":[ + { + "row":[ "10.0.2.168", "80", "0" @@ -88,17 +88,17 @@ resource "bigip_sys_iapp" "simplehttp" { } ] }, - { + { "name":"server_pools__servers" } ], "template":"/Common/f5.http", "templateModified":"no", -"templateReference":{ +"templateReference":{ "link":"https://localhost/mgmt/tm/sys/application/template/~Common~f5.http?ver=13.0.0" }, "trafficGroup":"/Common/traffic-group-1", -"trafficGroupReference":{ +"trafficGroupReference":{ "link":"https://localhost/mgmt/tm/cm/traffic-group/~Common~traffic-group-1?ver=13.0.0" }, "variables":[ diff --git a/examples/as3/main.tf b/examples/as3/main.tf index 9f2abc9bd..064564bb3 100644 --- a/examples/as3/main.tf +++ b/examples/as3/main.tf @@ -1,13 +1,78 @@ -provider "bigip" { - address = "xx.xx.xx.xxx" - username = "xxxx" - password = "xxxxxx" -} -resource "bigip_as3" "as3-example1" { - as3_json = file("as3_example1.json") +# +#resource "bigip_as3" "as3-example1" { +# as3_json = file("../examples/as3/as3_example1.json") +#} +# +#resource "bigip_as3" "as3-example2" { +# as3_json = file("../examples/as3/as3_example2.json") +#} + +resource "bigip_as3" "app-as3-irule" { + as3_json = < 30*time.Second { + backoff = 30 * time.Second // cap at 30 seconds + } + time.Sleep(backoff) + return b.pollingStatus(id, backoff*2) // recursive call with doubled delay } + return true } + func (b *BigIP) GetTenantList(body interface{}) (string, int, string) { tenantList := make([]string, 0) applicationList := make([]string, 0) diff --git a/vendor/github.com/f5devcentral/go-bigip/bigip.go b/vendor/github.com/f5devcentral/go-bigip/bigip.go index 16cd0c204..5b1d22c1e 100644 --- a/vendor/github.com/f5devcentral/go-bigip/bigip.go +++ b/vendor/github.com/f5devcentral/go-bigip/bigip.go @@ -19,8 +19,8 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" + "net/url" "os" "reflect" "strings" @@ -29,10 +29,15 @@ import ( var defaultConfigOptions = &ConfigOptions{ APICallTimeout: 60 * time.Second, + // Define new configuration options; are these user-override-able at the provider level or does that take more work? + TokenTimeout: 1200 * time.Second, + APICallRetries: 10, } type ConfigOptions struct { APICallTimeout time.Duration + TokenTimeout time.Duration + APICallRetries int } type Config struct { @@ -58,6 +63,7 @@ type BigIP struct { UserAgent string Teem bool ConfigOptions *ConfigOptions + Transaction string } // APIRequest builds our request before sending it to the server. @@ -98,20 +104,20 @@ func (r *RequestError) Error() error { // NewSession sets up our connection to the BIG-IP system. // func NewSession(host, port, user, passwd string, configOptions *ConfigOptions) *BigIP { func NewSession(bigipConfig *Config) *BigIP { - var url string + var urlString string if !strings.HasPrefix(bigipConfig.Address, "http") { - url = fmt.Sprintf("https://%s", bigipConfig.Address) + urlString = fmt.Sprintf("https://%s", bigipConfig.Address) } else { - url = bigipConfig.Address + urlString = bigipConfig.Address } if bigipConfig.Port != "" { - url = url + ":" + bigipConfig.Port + urlString = urlString + ":" + bigipConfig.Port } if bigipConfig.ConfigOptions == nil { bigipConfig.ConfigOptions = defaultConfigOptions } return &BigIP{ - Host: url, + Host: urlString, User: bigipConfig.Username, Password: bigipConfig.Password, Transport: &http.Transport{ @@ -219,49 +225,68 @@ func (client *BigIP) ValidateConnection() error { // APICall is used to query the BIG-IP web API. func (b *BigIP) APICall(options *APIRequest) ([]byte, error) { var req *http.Request - client := &http.Client{ - Transport: b.Transport, - Timeout: b.ConfigOptions.APICallTimeout, - } var format string if strings.Contains(options.URL, "mgmt/") { format = "%s/%s" } else { format = "%s/mgmt/tm/%s" } - url := fmt.Sprintf(format, b.Host, options.URL) - body := bytes.NewReader([]byte(options.Body)) - req, _ = http.NewRequest(strings.ToUpper(options.Method), url, body) - if b.Token != "" { - req.Header.Set("X-F5-Auth-Token", b.Token) - } else if options.URL != "mgmt/shared/authn/login" { - req.SetBasicAuth(b.User, b.Password) - } - - //fmt.Println("REQ -- ", options.Method, " ", url," -- ",options.Body) - - if len(options.ContentType) > 0 { - req.Header.Set("Content-Type", options.ContentType) - } - - res, err := client.Do(req) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - data, _ := ioutil.ReadAll(res.Body) + urlString := fmt.Sprintf(format, b.Host, options.URL) + maxRetries := b.ConfigOptions.APICallRetries + for i := 0; i < maxRetries; i++ { + body := bytes.NewReader([]byte(options.Body)) + req, _ = http.NewRequest(strings.ToUpper(options.Method), urlString, body) + b.Transport.Proxy = func(reqNew *http.Request) (*url.URL, error) { + return http.ProxyFromEnvironment(reqNew) + } + client := &http.Client{ + Transport: b.Transport, + Timeout: b.ConfigOptions.APICallTimeout, + } + if b.Token != "" { + req.Header.Set("X-F5-Auth-Token", b.Token) + } else if options.URL != "mgmt/shared/authn/login" { + req.SetBasicAuth(b.User, b.Password) + } - if res.StatusCode >= 400 { - if res.Header["Content-Type"][0] == "application/json" { - return data, b.checkError(data) + if len(b.Transaction) > 0 { + req.Header.Set("X-F5-REST-Coordination-Id", b.Transaction) } - return data, errors.New(fmt.Sprintf("HTTP %d :: %s", res.StatusCode, string(data[:]))) + if len(options.ContentType) > 0 { + req.Header.Set("Content-Type", options.ContentType) + } + res, err := client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + data, _ := io.ReadAll(res.Body) + contentType := "" + if ctHeaders, ok := res.Header["Content-Type"]; ok && len(ctHeaders) > 0 { + contentType = ctHeaders[0] + } + if res.StatusCode >= 400 { + if strings.Contains(contentType, "application/json") { + var reqError RequestError + err = json.Unmarshal(data, &reqError) + if err != nil { + return nil, err + } + // With how some of the requests come back from AS3, we sometimes have a nested error, so check the entire message for the "active asynchronous task" error + if res.StatusCode == 503 || reqError.Code == 503 || strings.Contains(strings.ToLower(reqError.Message), strings.ToLower("there is an active asynchronous task executing")) { + time.Sleep(10 * time.Second) + continue + } + return data, b.checkError(data) + } else { + return data, fmt.Errorf("HTTP %d :: %s", res.StatusCode, string(data[:])) + } + //return data, errors.New(fmt.Sprintf("HTTP %d :: %s", res.StatusCode, string(data[:]))) + } + return data, nil } - - return data, nil + return nil, fmt.Errorf("service unavailable after %d attempts", maxRetries) } func (b *BigIP) iControlPath(parts []string) string { @@ -418,10 +443,6 @@ func (b *BigIP) fastPatch(body interface{}, path ...string) ([]byte, error) { // Upload a file read from a Reader func (b *BigIP) Upload(r io.Reader, size int64, path ...string) (*Upload, error) { - client := &http.Client{ - Transport: b.Transport, - Timeout: b.ConfigOptions.APICallTimeout, - } options := &APIRequest{ Method: "post", URL: b.iControlPath(path), @@ -433,7 +454,7 @@ func (b *BigIP) Upload(r io.Reader, size int64, path ...string) (*Upload, error) } else { format = "%s/mgmt/%s" } - url := fmt.Sprintf(format, b.Host, options.URL) + urlString := fmt.Sprintf(format, b.Host, options.URL) chunkSize := 512 * 1024 var start, end int64 for { @@ -449,7 +470,7 @@ func (b *BigIP) Upload(r io.Reader, size int64, path ...string) (*Upload, error) chunk = chunk[:n] } body := bytes.NewReader(chunk) - req, _ := http.NewRequest(strings.ToUpper(options.Method), url, body) + req, _ := http.NewRequest(strings.ToUpper(options.Method), urlString, body) if b.Token != "" { req.Header.Set("X-F5-Auth-Token", b.Token) } else { @@ -457,12 +478,19 @@ func (b *BigIP) Upload(r io.Reader, size int64, path ...string) (*Upload, error) } req.Header.Add("Content-Type", options.ContentType) req.Header.Add("Content-Range", fmt.Sprintf("%d-%d/%d", start, end-1, size)) + b.Transport.Proxy = func(reqNew *http.Request) (*url.URL, error) { + return http.ProxyFromEnvironment(reqNew) + } + client := &http.Client{ + Transport: b.Transport, + Timeout: b.ConfigOptions.APICallTimeout, + } // Try to upload chunk res, err := client.Do(req) if err != nil { return nil, err } - data, _ := ioutil.ReadAll(res.Body) + data, _ := io.ReadAll(res.Body) if res.StatusCode >= 400 { if res.Header.Get("Content-Type") == "application/json" { return nil, b.checkError(data) @@ -484,7 +512,7 @@ func (b *BigIP) Upload(r io.Reader, size int64, path ...string) (*Upload, error) } } -// Get a url and populate an entity. If the entity does not exist (404) then the +// Get a urlString and populate an entity. If the entity does not exist (404) then the // passed entity will be untouched and false will be returned as the second parameter. // You can use this to distinguish between a missing entity or an actual error. func (b *BigIP) getForEntity(e interface{}, path ...string) (error, bool) { diff --git a/vendor/github.com/f5devcentral/go-bigip/ltm.go b/vendor/github.com/f5devcentral/go-bigip/ltm.go index cf259c239..ab2b370e2 100644 --- a/vendor/github.com/f5devcentral/go-bigip/ltm.go +++ b/vendor/github.com/f5devcentral/go-bigip/ltm.go @@ -675,6 +675,7 @@ type Policy struct { Name string PublishCopy string Partition string + Description string FullPath string Controls []string Requires []string @@ -685,6 +686,7 @@ type policyDTO struct { Name string `json:"name"` PublishCopy string `json:"publishedCopy"` Partition string `json:"partition,omitempty"` + Description string `json:"description"` Controls []string `json:"controls,omitempty"` Requires []string `json:"requires,omitempty"` Strategy string `json:"strategy,omitempty"` @@ -700,6 +702,7 @@ func (p *Policy) MarshalJSON() ([]byte, error) { PublishCopy: p.PublishCopy, Partition: p.Partition, Controls: p.Controls, + Description: p.Description, Requires: p.Requires, Strategy: p.Strategy, FullPath: p.FullPath, @@ -715,13 +718,13 @@ func (p *Policy) UnmarshalJSON(b []byte) error { if err != nil { return err } - p.Name = dto.Name p.PublishCopy = dto.PublishCopy p.Partition = dto.Partition p.Controls = dto.Controls p.Requires = dto.Requires p.Strategy = dto.Strategy + p.Description = dto.Description p.Rules = dto.Rules.Items p.FullPath = dto.FullPath @@ -2814,7 +2817,7 @@ func (b *BigIP) CheckDraftPolicy(name string, partition string) (bool, error) { if p.FullPath == "" { return false, nil } - return true , nil + return true, nil } func normalizePolicy(p *Policy) { diff --git a/vendor/github.com/f5devcentral/go-bigip/sys.go b/vendor/github.com/f5devcentral/go-bigip/sys.go index 4e3687bd0..668b08647 100644 --- a/vendor/github.com/f5devcentral/go-bigip/sys.go +++ b/vendor/github.com/f5devcentral/go-bigip/sys.go @@ -15,6 +15,7 @@ import ( "fmt" "log" "os" + //"strings" "time" ) @@ -285,6 +286,7 @@ const ( uriSslCert = "ssl-cert" uriSslKey = "ssl-key" uriDataGroup = "data-group" + uriTransaction = "transaction" REST_DOWNLOAD_PATH = "/var/config/rest/downloads" ) @@ -304,7 +306,7 @@ type Certificate struct { CreatedBy string `json:"createdBy,omitempty"` CreateTime string `json:"createTime,omitempty"` Email string `json:"email,omitempty"` - ExpirationDate int `json:"expirationDate,omitempty"` + ExpirationDate int64 `json:"expirationDate,omitempty"` ExpirationString string `json:"expirationString,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` FullPath string `json:"fullPath,omitempty"` @@ -360,6 +362,17 @@ type Key struct { UpdatedBy string `json:"updatedBy,omitempty"` } +type Transaction struct { + TransID int64 `json:"transId,omitempty"` + State string `json:"state,omitempty"` + TimeoutSeconds int64 `json:"timeoutSeconds,omitempty"` + AsyncExecution bool `json:"asyncExecution,omitempty"` + ValidateOnly bool `json:"validateOnly,omitempty"` + ExecutionTimeout int64 `json:"executionTimeout,omitempty"` + ExecutionTime int64 `json:"executionTime,omitempty"` + FailureReason string `json:"failureReason,omitempty"` +} + // Certificates returns a list of certificates. func (b *BigIP) Certificates() (*Certificates, error) { var certs Certificates @@ -779,6 +792,40 @@ func (b *BigIP) CreateTRAP(name string, authPasswordEncrypted string, authProtoc return b.post(config, uriSys, uriSnmp, uriTraps) } +func (b *BigIP) StartTransaction() (*Transaction, error) { + body := make(map[string]interface{}) + resp, err := b.postReq(body, uriMgmt, uriTm, uriTransaction) + + if err != nil { + return nil, fmt.Errorf("error encountered while starting transaction: %v", err) + } + transaction := &Transaction{} + err = json.Unmarshal(resp, transaction) + if err != nil { + return nil, err + } + log.Printf("[INFO] Transaction: %v", transaction) + b.Transaction = fmt.Sprint(transaction.TransID) + return transaction, nil +} + +func (b *BigIP) EndTransaction(tId int64) error { + commitTransaction := map[string]interface{}{ + "state": "VALIDATING", + "validateOnly": false, + } + payload, err := json.Marshal(commitTransaction) + if err != nil { + return fmt.Errorf("unable create commit transaction payload: %s", err) + } + err = b.patch(payload, uriMgmt, uriTm, uriTransaction, string(tId)) + if err != nil { + return fmt.Errorf("%s", err) + } + b.Transaction = "" + return nil +} + func (b *BigIP) ModifyTRAP(config *TRAP) error { return b.patch(config, uriSys, uriSnmp, uriTraps) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 4f4fe9fd8..f36cc04d6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -42,10 +42,10 @@ github.com/apparentlymart/go-textseg/v13/textseg # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew -# github.com/f5devcentral/go-bigip v0.0.0-20230629161824-6ac8bd4b0a16 +# github.com/f5devcentral/go-bigip v0.0.0-20230824164833-0e0156dbe878 ## explicit github.com/f5devcentral/go-bigip -# github.com/f5devcentral/go-bigip/f5teem v0.0.0-20230629161824-6ac8bd4b0a16 +# github.com/f5devcentral/go-bigip/f5teem v0.0.0-20230824164833-0e0156dbe878 ## explicit; go 1.13 github.com/f5devcentral/go-bigip/f5teem # github.com/fatih/color v1.13.0