From 8e4d7ce42368bd07face5eb3db80c4072dc4724b Mon Sep 17 00:00:00 2001
From: ramaniprateek
Date: Mon, 10 Jun 2024 13:58:35 +0530
Subject: [PATCH 1/4] issue-987 fixed
---
bigip/resource_bigip_as3.go | 31 +++++++++++++++++++------------
1 file changed, 19 insertions(+), 12 deletions(-)
diff --git a/bigip/resource_bigip_as3.go b/bigip/resource_bigip_as3.go
index e32bb0e2..f2e04854 100644
--- a/bigip/resource_bigip_as3.go
+++ b/bigip/resource_bigip_as3.go
@@ -294,7 +294,7 @@ func resourceBigipAs3Read(ctx context.Context, d *schema.ResourceData, meta inte
as3Json := d.Get("as3_json").(string)
perappMode := d.Get("per_app_mode").(bool)
log.Printf("[INFO] AS3 config:%+v", as3Json)
- if d.Get("as3_json") != nil && !perappMode {
+ if d.Get("as3_json") != nil && !perappMode && d.Get("tenant_filter") == "" {
tList, _, _ = client.GetTenantList(as3Json)
if createdTenants != "" && createdTenants != tList {
tList = createdTenants
@@ -310,17 +310,7 @@ func resourceBigipAs3Read(ctx context.Context, d *schema.ResourceData, meta inte
log.Printf("[DEBUG] Applications in AS3 get call : %s", applicationList)
if name != "" {
as3Resp, err := client.GetAs3(name, applicationList, d.Get("per_app_mode").(bool))
- if d.Get("per_app_mode").(bool) {
- as3Json := make(map[string]interface{})
- _ = json.Unmarshal([]byte(as3Resp), &as3Json)
- out, _ := json.Marshal(as3Json)
- as3Dec := string(out)
- applicationList = client.GetAppsList(fmt.Sprintf("%v", as3Dec))
- log.Printf("[DEBUG] Application List from retreived the GET call in Read function : %s", applicationList)
- _ = d.Set("application_list", applicationList)
- }
- log.Printf("[DEBUG] AS3 json retreived from the GET call in Read function : %s", as3Resp)
if err != nil {
log.Printf("[ERROR] Unable to retrieve json ")
if err.Error() == "unexpected end of JSON input" {
@@ -336,7 +326,24 @@ func resourceBigipAs3Read(ctx context.Context, d *schema.ResourceData, meta inte
// d.SetId("")
return nil
}
- _ = d.Set("as3_json", as3Resp)
+
+ if d.Get("per_app_mode").(bool) {
+ as3Json := make(map[string]interface{})
+ filteredAs3Json := make(map[string]interface{})
+ _ = json.Unmarshal([]byte(as3Resp), &as3Json)
+ for _, value := range strings.Split(applicationList, ",") {
+ log.Printf("[DEBUG] Fetching AS3 get for Application : %s", value)
+ filteredAs3Json[value] = as3Json[value]
+ }
+ filteredAs3Json["schemaVersion"] = as3Json["schemaVersion"]
+ out, _ := json.Marshal(filteredAs3Json)
+ filteredAs3String := string(out)
+ log.Printf("[DEBUG] AS3 GET call in Read function : %s", filteredAs3Json)
+ _ = d.Set("as3_json", filteredAs3String)
+ } else {
+ _ = d.Set("as3_json", as3Resp)
+ }
+
_ = d.Set("tenant_list", name)
} else if d.Get("task_id") != nil {
taskResponse, err := client.Getas3TaskResponse(d.Get("task_id").(string))
From f971baf15d7afb9d773574b0fe775f623808c53c Mon Sep 17 00:00:00 2001
From: ramaniprateek
Date: Tue, 11 Jun 2024 16:15:57 +0530
Subject: [PATCH 2/4] Changes for Per-App Delete Added
---
bigip/resource_bigip_as3.go | 31 +++--
bigip/resource_bigip_as3_test.go | 126 ++++++++++++++++++
examples/as3/as3_per_app_example1.json | 25 ++++
examples/as3/as3_per_app_example2.json | 25 ++++
examples/as3/bigip_as3.tf | 6 +
examples/as3/perApplication_example.json | 47 +++++++
.../f5devcentral/go-bigip/as3bigip.go | 9 ++
7 files changed, 260 insertions(+), 9 deletions(-)
create mode 100644 examples/as3/as3_per_app_example1.json
create mode 100644 examples/as3/as3_per_app_example2.json
create mode 100644 examples/as3/perApplication_example.json
diff --git a/bigip/resource_bigip_as3.go b/bigip/resource_bigip_as3.go
index f2e04854..140899d0 100644
--- a/bigip/resource_bigip_as3.go
+++ b/bigip/resource_bigip_as3.go
@@ -452,20 +452,33 @@ func resourceBigipAs3Delete(ctx context.Context, d *schema.ResourceData, meta in
tList, _, _ = client.GetTenantList(d.Get("as3_json").(string))
}
- if d.Id() != "" && tList != "" {
+ if d.Id() != "" && tList != "" && d.Get("tenant_filter") == "" {
name = tList
} 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)
- return diag.FromErr(err)
- }
- if failedTenants != "" {
- _ = d.Set("tenant_list", name)
- return resourceBigipAs3Read(ctx, d, meta)
+ if d.Get("per_app_mode").(bool) {
+ applicationList := d.Get("application_list").(string)
+ log.Printf("[INFO] Deleting As3 config for Applications:%+v", applicationList)
+ for _, appName := range strings.Split(applicationList, ",") {
+ log.Printf("[INFO] Deleting AS3 for Application : %s", appName)
+ err := client.DeletePerApplicationAs3Bigip(name, appName)
+ if err != nil {
+ log.Printf("[ERROR] Unable to DeleteContext: %v :", err)
+ return diag.FromErr(err)
+ }
+ }
+ } else {
+ err, failedTenants := client.DeleteAs3Bigip(name)
+ if err != nil {
+ log.Printf("[ERROR] Unable to DeleteContext: %v :", err)
+ return diag.FromErr(err)
+ }
+ if failedTenants != "" {
+ _ = d.Set("tenant_list", name)
+ return resourceBigipAs3Read(ctx, d, meta)
+ }
}
d.SetId("")
return nil
diff --git a/bigip/resource_bigip_as3_test.go b/bigip/resource_bigip_as3_test.go
index c118cd80..c68c3bca 100644
--- a/bigip/resource_bigip_as3_test.go
+++ b/bigip/resource_bigip_as3_test.go
@@ -14,6 +14,7 @@ import (
"net/http"
"os"
"regexp"
+ "strings"
"testing"
bigip "github.com/f5devcentral/go-bigip"
@@ -98,6 +99,28 @@ resource "bigip_as3" "perapp2" {
ignore_metadata = true
}
`
+var TestAs3PerAppResource = `
+resource "bigip_as3" "as3-example" {
+ tenant_name = "dmz"
+ as3_json = "${file("` + dir + `/../examples/as3/perApplication_example.json")}"
+}
+`
+var TestAs3PerAppResource1 = `
+resource "bigip_as3" "as3-example1" {
+ tenant_name = "dmz"
+ as3_json = "${file("` + dir + `/../examples/as3/as3_per_app_example1.json")}"
+}
+`
+var TestAs3PerAppResource2 = `
+resource "bigip_as3" "as3-example1" {
+ tenant_name = "dmz"
+ as3_json = "${file("` + dir + `/../examples/as3/as3_per_app_example1.json")}"
+}
+resource "bigip_as3" "as3-example2" {
+ tenant_name = "dmz"
+ as3_json = "${file("` + dir + `/../examples/as3/as3_per_app_example2.json")}"
+}
+`
func TestAccBigipAs3_create_SingleTenant(t *testing.T) {
resource.Test(t, resource.TestCase{
@@ -434,6 +457,46 @@ func testCheckAs3Exists(name string, exists bool) resource.TestCheckFunc {
}
}
+func testCheckAS3AppExists(tenantName, appNames string, exists bool) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ clientBigip := testAccProvider.Meta().(*bigip.BigIP)
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
+ client := &http.Client{Transport: tr}
+
+ for _, appName := range strings.Split(appNames, ",") {
+ url := clientBigip.Host + "/mgmt/shared/appsvcs/declare/" + tenantName + "/applications/" + appName
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return fmt.Errorf("[ERROR] Error while creating http request with AS3 json: %v", err)
+ }
+ req.SetBasicAuth(clientBigip.User, clientBigip.Password)
+ req.Header.Set("Accept", "application/json")
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return err
+ }
+
+ defer func() {
+ if err := resp.Body.Close(); err != nil {
+ log.Printf("[DEBUG] Could not close the request to %s", url)
+ }
+ }()
+ var body bytes.Buffer
+ _, err = io.Copy(&body, resp.Body)
+ // body, err := ioutil.ReadAll(resp.Body)
+ bodyString := body.String()
+ if (resp.Status == "204 No Content" || err != nil || resp.StatusCode == 404) && exists {
+ return fmt.Errorf("[ERROR] Error while checking as3resource present in bigip :%s %v", bodyString, err)
+ }
+ }
+
+ return nil
+ }
+}
+
func TestAccBigipAs3_badJSON(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() {
@@ -464,3 +527,66 @@ func testCheckAs3Destroy(s *terraform.State) error {
}
return nil
}
+
+func TestAccBigipPer_AppAs3_SingleTenant(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAcctPreCheck(t)
+ },
+ Providers: testAccProviders,
+ CheckDestroy: testCheckAs3Destroy,
+ Steps: []resource.TestStep{
+ {
+ Config: TestAs3PerAppResource,
+ Check: resource.ComposeTestCheckFunc(
+ testCheckAs3Exists("dmz", true),
+ testCheckAS3AppExists("dmz", "Application1,Application2", true),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccBigipPer_AppAs3_update_addApplication(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAcctPreCheck(t)
+ },
+ Providers: testAccProviders,
+ CheckDestroy: testCheckAs3Destroy,
+ Steps: []resource.TestStep{
+ {
+ Config: TestAs3PerAppResource1,
+
+ Check: resource.ComposeTestCheckFunc(
+ testCheckAs3Exists("dmz", true),
+ testCheckAS3AppExists("dmz", "path_app1", true),
+ ),
+ },
+ {
+ Config: TestAs3PerAppResource2,
+ Check: resource.ComposeTestCheckFunc(
+ testCheckAs3Exists("dmz", true),
+ testCheckAS3AppExists("dmz", "path_app1,path_app2", true),
+ ),
+ },
+ },
+ })
+}
+
+// Per-App mode is disabled
+func TestAccBigipPer_AppAs3_update_invalidJson(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ testAcctPreCheck(t)
+ },
+ Providers: testAccProviders,
+ CheckDestroy: testCheckAs3Destroy,
+ Steps: []resource.TestStep{
+ {
+ Config: TestAs3PerAppResource1,
+ ExpectError: regexp.MustCompile("Invalid request value"),
+ },
+ },
+ })
+}
diff --git a/examples/as3/as3_per_app_example1.json b/examples/as3/as3_per_app_example1.json
new file mode 100644
index 00000000..d29e374e
--- /dev/null
+++ b/examples/as3/as3_per_app_example1.json
@@ -0,0 +1,25 @@
+{
+ "schemaVersion": "3.50.0",
+ "path_app1": {
+ "class": "Application",
+ "vs_name_app1": {
+ "class": "Service_HTTP",
+ "virtualAddresses": [
+ "192.1.1.24"
+ ],
+ "pool": "pool"
+ },
+ "pool": {
+ "class": "Pool",
+ "members": [
+ {
+ "servicePort": 80,
+ "serverAddresses": [
+ "192.20.1.10",
+ "192.30.1.20"
+ ]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/as3/as3_per_app_example2.json b/examples/as3/as3_per_app_example2.json
new file mode 100644
index 00000000..8e061c5e
--- /dev/null
+++ b/examples/as3/as3_per_app_example2.json
@@ -0,0 +1,25 @@
+{
+ "schemaVersion": "3.50.0",
+ "path_app2": {
+ "class": "Application",
+ "vs_name_app2": {
+ "class": "Service_HTTP",
+ "virtualAddresses": [
+ "192.1.1.234"
+ ],
+ "pool": "pool"
+ },
+ "pool": {
+ "class": "Pool",
+ "members": [
+ {
+ "servicePort": 80,
+ "serverAddresses": [
+ "12.20.1.10",
+ "12.30.1.20"
+ ]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/as3/bigip_as3.tf b/examples/as3/bigip_as3.tf
index aabb5b3a..fcd1dc62 100644
--- a/examples/as3/bigip_as3.tf
+++ b/examples/as3/bigip_as3.tf
@@ -25,4 +25,10 @@ resource "bigip_as3" "as3-example1" {
depends_on = ["null_resource.install_as3"]
}
+// Per-Application Deployment example
+resource "bigip_as3" "as3-example1" {
+ as3_json = file("perApplication_example.json")
+ tenant_name = "Test"
+}
+
diff --git a/examples/as3/perApplication_example.json b/examples/as3/perApplication_example.json
new file mode 100644
index 00000000..f1886834
--- /dev/null
+++ b/examples/as3/perApplication_example.json
@@ -0,0 +1,47 @@
+{
+ "schemaVersion": "3.50.0",
+ "Application1": {
+ "class": "Application",
+ "service": {
+ "class": "Service_HTTP",
+ "virtualAddresses": [
+ "192.0.2.1"
+ ],
+ "pool": "pool"
+ },
+ "pool": {
+ "class": "Pool",
+ "members": [
+ {
+ "servicePort": 80,
+ "serverAddresses": [
+ "192.0.2.10",
+ "192.0.2.20"
+ ]
+ }
+ ]
+ }
+ },
+ "Application2": {
+ "class": "Application",
+ "service": {
+ "class": "Service_HTTP",
+ "virtualAddresses": [
+ "192.0.3.2"
+ ],
+ "pool": "pool"
+ },
+ "pool": {
+ "class": "Pool",
+ "members": [
+ {
+ "servicePort": 80,
+ "serverAddresses": [
+ "192.0.3.30",
+ "192.0.3.40"
+ ]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/vendor/github.com/f5devcentral/go-bigip/as3bigip.go b/vendor/github.com/f5devcentral/go-bigip/as3bigip.go
index 1016b39d..6e714c57 100644
--- a/vendor/github.com/f5devcentral/go-bigip/as3bigip.go
+++ b/vendor/github.com/f5devcentral/go-bigip/as3bigip.go
@@ -580,6 +580,15 @@ func (b *BigIP) CheckSetting() (bool, error) {
// return perAppDeploymentAllowed, nil
}
+func (b *BigIP) DeletePerApplicationAs3Bigip(tenantName string, applicationName string) error {
+
+ _, err := b.deleteReq(uriMgmt, uriShared, uriAppsvcs, uriDeclare, tenantName, uriApplications, applicationName)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func (b *BigIP) AddServiceDiscoveryNodes(taskid string, config []interface{}) error {
resp, err := b.postReq(config, uriMgmt, uriShared, "service-discovery", "task", taskid, "nodes")
if err != nil {
From 9208e956891091760b010c032b18ac04ee6def6a Mon Sep 17 00:00:00 2001
From: ramaniprateek
Date: Tue, 11 Jun 2024 16:27:14 +0530
Subject: [PATCH 3/4] Doc Added for Per-App
---
docs/resources/bigip_as3.md | 48 ++++++++++++++++++++++++++++++-------
1 file changed, 39 insertions(+), 9 deletions(-)
diff --git a/docs/resources/bigip_as3.md b/docs/resources/bigip_as3.md
index 89a329d2..0e4c63b8 100644
--- a/docs/resources/bigip_as3.md
+++ b/docs/resources/bigip_as3.md
@@ -19,6 +19,11 @@ This resource is helpful to configure AS3 declarative JSON on BIG-IP.
~> For Deploying AS3 JSON in Per-App mode, this resource provided with a attribute [tenant_name](#tenant_name) to be passed to add application on specified tenant, else random tenant name will be generated.
+As3 Declaration can be deployed in Traditional way as well as Per-Application Way :
+
+- Traditional Way - Entire Declaration needs to be passed in during the create and update call along with the tenant details in the declaration.
+- Per-Application Way - Only Specific application/s details needs to be passed and rest application can be added in the update call , without passing the entire declaration. Tenant name needs to be passed else random tenant name will be generated. PerApplication needs to be turned `true` as a Prerequisite on the Big-IP (BIG-IP AS3 version >3.50) device. For details :
+
## Example Usage
```hcl
@@ -35,7 +40,33 @@ resource "bigip_as3" "as3-example1" {
}
```
-## Example Usage for Per-App mode deployment
+# Per-Application Deployment - Example Usage for json file with tenant name
+resource "bigip_as3" "as3-example1" {
+ as3_json = file("perApplication_example.json")
+ tenant_name = "Test"
+}
+
+# Per-Application Deployment - Example Usage for json file without tenant name - Tenant with Random name is generated in this case
+resource "bigip_as3" "as3-example1" {
+ as3_json = file("perApplication_example.json")
+}
+
+# Per-Application Deployment - Delete Example
+resource "bigip_as3" "as3-example1" {
+ tenant_name = "Test"
+ as3_json = file("as3_per_app_example1.json")
+
+}
+
+resource "bigip_as3" "as3-example2" {
+ tenant_name = "Test"
+ as3_json = file("as3_per_app_example2.json")
+
+}
+
+on running above 2 resources , we will be able to deploy Applications - `path_app1 , path_app2` on Tenant `Test`
+now, if we run `terraform destroy -target=bigip_as3.as3-example2` , only `path_app2` will be deleted from Tenant.
+
[perApplication as3](#perApplication_example)
@@ -200,13 +231,12 @@ resource "bigip_as3" "as3-example1" {
```json
{
- "schemaVersion": "3.50.1",
"Application1": {
"class": "Application",
"service": {
"class": "Service_HTTP",
"virtualAddresses": [
- "192.1.1.1"
+ "192.0.2.1"
],
"pool": "pool"
},
@@ -216,19 +246,19 @@ resource "bigip_as3" "as3-example1" {
{
"servicePort": 80,
"serverAddresses": [
- "192.0.1.10",
- "192.0.1.20"
+ "192.0.2.10",
+ "192.0.2.20"
]
}
]
}
- },
+ },
"Application2": {
"class": "Application",
"service": {
"class": "Service_HTTP",
"virtualAddresses": [
- "192.1.2.1"
+ "192.0.3.2"
],
"pool": "pool"
},
@@ -238,8 +268,8 @@ resource "bigip_as3" "as3-example1" {
{
"servicePort": 80,
"serverAddresses": [
- "192.0.2.10",
- "192.0.2.20"
+ "192.0.3.30",
+ "192.0.3.40"
]
}
]
From 848b8c541e641d6a0c8448d3c4ca02dca2228080 Mon Sep 17 00:00:00 2001
From: ramaniprateek
Date: Wed, 12 Jun 2024 10:27:44 +0530
Subject: [PATCH 4/4] Doc changes
---
docs/resources/bigip_as3.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/resources/bigip_as3.md b/docs/resources/bigip_as3.md
index 0e4c63b8..657498f6 100644
--- a/docs/resources/bigip_as3.md
+++ b/docs/resources/bigip_as3.md
@@ -22,7 +22,9 @@ This resource is helpful to configure AS3 declarative JSON on BIG-IP.
As3 Declaration can be deployed in Traditional way as well as Per-Application Way :
- Traditional Way - Entire Declaration needs to be passed in during the create and update call along with the tenant details in the declaration.
-- Per-Application Way - Only Specific application/s details needs to be passed and rest application can be added in the update call , without passing the entire declaration. Tenant name needs to be passed else random tenant name will be generated. PerApplication needs to be turned `true` as a Prerequisite on the Big-IP (BIG-IP AS3 version >3.50) device. For details :
+- Per-Application Way - Only application details needs to be passed in the as3_json. Tenant name needs to be passed else random tenant name will be generated.
+
+**Note:** : PerApplication needs to be turned `true` as a Prerequisite on the Big-IP (BIG-IP AS3 version >3.50) device. For details :
## Example Usage
@@ -64,7 +66,7 @@ resource "bigip_as3" "as3-example2" {
}
-on running above 2 resources , we will be able to deploy Applications - `path_app1 , path_app2` on Tenant `Test`
+On running above 2 resources , we will be able to deploy Applications - `path_app1 , path_app2` on Tenant `Test`
now, if we run `terraform destroy -target=bigip_as3.as3-example2` , only `path_app2` will be deleted from Tenant.