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