Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue-987 fixed #990

Merged
merged 4 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 41 additions & 21 deletions bigip/resource_bigip_as3.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@
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
Expand All @@ -310,17 +310,7 @@
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" {
Expand All @@ -336,7 +326,24 @@
// 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))
Expand Down Expand Up @@ -445,20 +452,33 @@
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)

Check failure on line 466 in bigip/resource_bigip_as3.go

View workflow job for this annotation

GitHub Actions / golint

client.DeletePerApplicationAs3Bigip undefined (type *"github.com/f5devcentral/go-bigip".BigIP has no field or method DeletePerApplicationAs3Bigip)) (typecheck)

Check failure on line 466 in bigip/resource_bigip_as3.go

View workflow job for this annotation

GitHub Actions / golint

client.DeletePerApplicationAs3Bigip undefined (type *"github.com/f5devcentral/go-bigip".BigIP has no field or method DeletePerApplicationAs3Bigip) (typecheck)
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
Expand Down
126 changes: 126 additions & 0 deletions bigip/resource_bigip_as3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/http"
"os"
"regexp"
"strings"
"testing"

bigip "github.com/f5devcentral/go-bigip"
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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"),
},
},
})
}
50 changes: 41 additions & 9 deletions docs/resources/bigip_as3.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ 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 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 : <https://clouddocs.f5.com/products/extensions/f5-appsvcs-extension/latest/userguide/per-app-declarations.html>

## Example Usage

```hcl
Expand All @@ -35,7 +42,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)

Expand Down Expand Up @@ -200,13 +233,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"
},
Expand All @@ -216,19 +248,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"
},
Expand All @@ -238,8 +270,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"
]
}
]
Expand Down
25 changes: 25 additions & 0 deletions examples/as3/as3_per_app_example1.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
]
}
}
}
25 changes: 25 additions & 0 deletions examples/as3/as3_per_app_example2.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
]
}
}
}
Loading
Loading