diff --git a/citrixadc/data_source_citrixadc_hanode_test.go b/citrixadc/data_source_citrixadc_hanode_test.go
index 0c7d6daca..5e03cabdb 100644
--- a/citrixadc/data_source_citrixadc_hanode_test.go
+++ b/citrixadc/data_source_citrixadc_hanode_test.go
@@ -25,7 +25,7 @@ import (
const testAccDataSourceHanode = `
data "citrixadc_hanode" "hanode" {
hanode_id = 0
- }
+}
`
func TestAccDataSourceHanode_basic(t *testing.T) {
diff --git a/citrixadc/helpers_test.go b/citrixadc/helpers_test.go
index 5ec13bf8f..cd6a543aa 100644
--- a/citrixadc/helpers_test.go
+++ b/citrixadc/helpers_test.go
@@ -17,6 +17,34 @@ import (
"github.com/citrix/adc-nitro-go/service"
)
+func doSslcertkeyPreChecks(t *testing.T) {
+ testAccPreCheck(t)
+
+ uploads := []string{
+ "ca.crt",
+ "intermediate.crt",
+ "certificate1.crt",
+ "certificate2.crt",
+ "certificate3.crt",
+ "key1.pem",
+ "key2.pem",
+ "key3.pem",
+ }
+
+ c, err := testHelperInstantiateClient("", "", "", false)
+ if err != nil {
+ t.Fatalf("Failed to instantiate client. %v\n", err)
+ }
+
+ //c := testAccProvider.Meta().(*NetScalerNitroClient)
+ for _, filename := range uploads {
+ err := uploadTestdataFile(c, t, filename, "/var/tmp")
+ if err != nil {
+ t.Errorf("%v", err)
+ }
+ }
+}
+
func uploadTestdataFile(c *NetScalerNitroClient, t *testing.T, filename, targetDir string) error {
client := c.client
diff --git a/citrixadc/provider.go b/citrixadc/provider.go
index 00e0a143a..de1ad3674 100644
--- a/citrixadc/provider.go
+++ b/citrixadc/provider.go
@@ -112,35 +112,35 @@ func providerDataSources() map[string]*schema.Resource {
func providerResources() map[string]*schema.Resource {
return map[string]*schema.Resource{
- "citrixadc_lbmetrictable": resourceCitrixAdcLbmetrictable(),
- "citrixadc_sslservice_sslcertkey_binding": resourceCitrixAdcSslservice_sslcertkey_binding(),
- "citrixadc_sslservice_sslciphersuite_binding": resourceCitrixAdcSslservice_sslciphersuite_binding(),
- "citrixadc_sslservice_ecccurve_binding": resourceCitrixAdcSslservice_ecccurve_binding(),
- "citrixadc_sslservice": resourceCitrixAdcSslservice(),
- "citrixadc_sslfipskey": resourceCitrixAdcSslfipskey(),
- "citrixadc_sslhsmkey": resourceCitrixAdcSslhsmkey(),
- "citrixadc_sslcacertgroup_sslcertkey_binding": resourceCitrixAdcSslcacertgroup_sslcertkey_binding(),
- "citrixadc_lbroute6": resourceCitrixAdcLbroute6(),
- "citrixadc_sslpolicylabel": resourceCitrixAdcSslpolicylabel(),
- "citrixadc_ssllogprofile": resourceCitrixAdcSsllogprofile(),
- "citrixadc_sslprofile_sslcertkey_binding": resourceCitrixAdcSslprofile_sslcertkey_binding(),
- "citrixadc_sslpolicylabel_sslpolicy_binding": resourceCitrixAdcSslpolicylabel_sslpolicy_binding(),
- "citrixadc_lbvserver_botpolicy_binding": resourceCitrixAdcLbvserver_botpolicy_binding(),
- "citrixadc_lbvserver_auditsyslogpolicy_binding": resourceCitrixAdcLbvserver_auditsyslogpolicy_binding(),
- "citrixadc_sslcacertgroup": resourceCitrixAdcSslcacertgroup(),
- "citrixadc_botsettings": resourceCitrixAdcBotsettings(),
- "citrixadc_botpolicy": resourceCitrixAdcBotpolicy(),
- "citrixadc_lbvserver_analyticsprofile_binding": resourceCitrixAdcLbvserver_analyticsprofile_binding(),
- "citrixadc_lbvserver_appqoepolicy_binding": resourceCitrixAdcLbvserver_appqoepolicy_binding(),
- "citrixadc_lbmonitor_metric_binding": resourceCitrixAdcLbmonitor_metric_binding(),
- "citrixadc_lbvserver": resourceCitrixAdcLbvserver(),
- "citrixadc_service": resourceCitrixAdcService(),
- "citrixadc_csvserver": resourceCitrixAdcCsvserver(),
- "citrixadc_cspolicy": resourceCitrixAdcCspolicy(),
- "citrixadc_csaction": resourceCitrixAdcCsaction(),
- "citrixadc_sslaction": resourceCitrixAdcSslaction(),
- "citrixadc_sslpolicy": resourceCitrixAdcSslpolicy(),
- "citrixadc_sslcertkey": resourceCitrixAdcSslcertkey(),
+ "citrixadc_lbmetrictable": resourceCitrixAdcLbmetrictable(),
+ "citrixadc_sslservice_sslcertkey_binding": resourceCitrixAdcSslservice_sslcertkey_binding(),
+ "citrixadc_sslservice_sslciphersuite_binding": resourceCitrixAdcSslservice_sslciphersuite_binding(),
+ "citrixadc_sslservice_ecccurve_binding": resourceCitrixAdcSslservice_ecccurve_binding(),
+ "citrixadc_sslservice": resourceCitrixAdcSslservice(),
+ "citrixadc_sslfipskey": resourceCitrixAdcSslfipskey(),
+ "citrixadc_sslhsmkey": resourceCitrixAdcSslhsmkey(),
+ "citrixadc_sslcacertgroup_sslcertkey_binding": resourceCitrixAdcSslcacertgroup_sslcertkey_binding(),
+ "citrixadc_lbroute6": resourceCitrixAdcLbroute6(),
+ "citrixadc_sslpolicylabel": resourceCitrixAdcSslpolicylabel(),
+ "citrixadc_ssllogprofile": resourceCitrixAdcSsllogprofile(),
+ "citrixadc_sslprofile_sslcertkey_binding": resourceCitrixAdcSslprofile_sslcertkey_binding(),
+ "citrixadc_sslpolicylabel_sslpolicy_binding": resourceCitrixAdcSslpolicylabel_sslpolicy_binding(),
+ "citrixadc_lbvserver_botpolicy_binding": resourceCitrixAdcLbvserver_botpolicy_binding(),
+ "citrixadc_lbvserver_auditsyslogpolicy_binding": resourceCitrixAdcLbvserver_auditsyslogpolicy_binding(),
+ "citrixadc_sslcacertgroup": resourceCitrixAdcSslcacertgroup(),
+ "citrixadc_botsettings": resourceCitrixAdcBotsettings(),
+ "citrixadc_botpolicy": resourceCitrixAdcBotpolicy(),
+ "citrixadc_lbvserver_analyticsprofile_binding": resourceCitrixAdcLbvserver_analyticsprofile_binding(),
+ "citrixadc_lbvserver_appqoepolicy_binding": resourceCitrixAdcLbvserver_appqoepolicy_binding(),
+ "citrixadc_lbmonitor_metric_binding": resourceCitrixAdcLbmonitor_metric_binding(),
+ "citrixadc_lbvserver": resourceCitrixAdcLbvserver(),
+ "citrixadc_service": resourceCitrixAdcService(),
+ "citrixadc_csvserver": resourceCitrixAdcCsvserver(),
+ "citrixadc_cspolicy": resourceCitrixAdcCspolicy(),
+ "citrixadc_csaction": resourceCitrixAdcCsaction(),
+ "citrixadc_sslaction": resourceCitrixAdcSslaction(),
+ "citrixadc_sslpolicy": resourceCitrixAdcSslpolicy(),
+ // "citrixadc_sslcertkey": resourceCitrixAdcSslcertkey(),
"citrixadc_sslprofile": resourceCitrixAdcSslprofile(),
"citrixadc_sslparameter": resourceCitrixAdcSslparameter(),
"citrixadc_ssldhparam": resourceCitrixAdcSsldhparam(),
diff --git a/citrixadc_framework/acctests/helpers_test.go b/citrixadc_framework/acctests/helpers_test.go
new file mode 100644
index 000000000..6fd7678b5
--- /dev/null
+++ b/citrixadc_framework/acctests/helpers_test.go
@@ -0,0 +1,196 @@
+package acctests
+
+import (
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path"
+ "regexp"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+
+ "github.com/citrix/adc-nitro-go/resource/config/system"
+ "github.com/citrix/adc-nitro-go/service"
+)
+
+// NetScalerNitroClient is a wrapper around the service.NitroClient
+type NetScalerNitroClient struct {
+ Username string
+ Password string
+ Endpoint string
+ client *service.NitroClient
+ lock sync.Mutex
+}
+
+func doSslcertkeyPreChecks(t *testing.T) {
+ testAccPreCheck(t)
+
+ uploads := []string{
+ "ca.crt",
+ "intermediate.crt",
+ "certificate1.crt",
+ "certificate2.crt",
+ "certificate3.crt",
+ "key1.pem",
+ "key2.pem",
+ "key3.pem",
+ "servercert1.cert",
+ "servercert1.key",
+ "intermediate1.cert",
+ "rootcert1.cert",
+ "servercert2.cert",
+ "servercert2.key",
+ "servercert3.cert",
+ "servercert3.key",
+ }
+
+ c, err := testHelperInstantiateClient("", "", "", false)
+ if err != nil {
+ t.Fatalf("Failed to instantiate client. %v\n", err)
+ }
+
+ //c := testAccProvider.Meta().(*NetScalerNitroClient)
+ for _, filename := range uploads {
+ err := uploadTestdataFile(c, t, filename, "/nsconfig/ssl")
+ if err != nil {
+ t.Errorf("%v", err)
+ }
+ }
+}
+
+func uploadTestdataFile(c *NetScalerNitroClient, t *testing.T, filename, targetDir string) error {
+ client := c.client
+
+ // Get here path
+ _, here_filename, _, _ := runtime.Caller(1)
+ b, err := ioutil.ReadFile(path.Join(path.Dir(here_filename), "testdata", filename))
+
+ if err != nil {
+ return err
+ }
+
+ sf := system.Systemfile{
+ Filename: filename,
+ Filecontent: base64.StdEncoding.EncodeToString(b),
+ Filelocation: targetDir,
+ }
+ _, err = client.AddResource(service.Systemfile.Type(), filename, &sf)
+ if err != nil && strings.Contains(err.Error(), "File already exists") {
+ url_args := map[string]string{"filelocation": strings.Replace(targetDir, "/", "%2F", -1)}
+ err := client.DeleteResourceWithArgsMap(service.Systemfile.Type(), filename, url_args)
+ if err != nil {
+ return err
+ }
+ _, err = client.AddResource(service.Systemfile.Type(), filename, &sf)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+var helperClient *NetScalerNitroClient
+
+func testHelperInstantiateClient(nsUrl, username, password string, sslVerify bool) (*NetScalerNitroClient, error) {
+ if helperClient != nil {
+ log.Printf("Returning existing helper client\n")
+ return helperClient, nil
+ }
+
+ if nsUrl == "" {
+ if nsUrl = os.Getenv("NS_URL"); nsUrl == "" {
+ return nil, errors.New("No nsUrl defined")
+ }
+ }
+
+ if username == "" {
+ if username = os.Getenv("NS_LOGIN"); username == "" {
+ username = "nsroot"
+ }
+ }
+
+ if password == "" {
+ if password = os.Getenv("NS_PASSWORD"); password == "" {
+ password = "nsroot"
+ }
+ }
+
+ c := NetScalerNitroClient{
+ Username: username,
+ Password: password,
+ Endpoint: nsUrl,
+ }
+
+ params := service.NitroParams{
+ Url: nsUrl,
+ Username: username,
+ Password: password,
+ //ProxiedNs: d.Get("proxied_ns").(string),
+ SslVerify: sslVerify,
+ }
+ client, err := service.NewNitroClientFromParams(params)
+ if err != nil {
+ return nil, err
+ }
+
+ c.client = client
+ helperClient = &c
+ log.Printf("Helper client instantiated\n")
+
+ return helperClient, nil
+}
+
+func testHelperEnsureResourceDeletion(c *NetScalerNitroClient, t *testing.T, resourceType, resourceName string, deleteArgsMap map[string]string) {
+ if _, err := c.client.FindResource(resourceType, resourceName); err != nil {
+ targetSubstring := fmt.Sprintf("No resource %s of type %s found", resourceName, resourceType)
+ actualError := err.Error()
+ t.Logf("targetSubstring \"%s\"", targetSubstring)
+ t.Logf("actualError \"%s\"", actualError)
+ if strings.Contains(err.Error(), targetSubstring) {
+ t.Logf("Ensure delete found no remaining resource %s", resourceName)
+ return
+ } else {
+ t.Fatalf("Unexpected error while ensuring delete of resource %v. %v", resourceName, err)
+ return
+ }
+ }
+
+ // Fallthrough
+ if deleteArgsMap == nil {
+ if err := c.client.DeleteResource(resourceType, resourceName); err != nil {
+ t.Logf("Ensuring delete failed for resource %s.", resourceName)
+ t.Fatal(err)
+ return
+ } else {
+ t.Logf("Ensuring deletion of %s successful", resourceName)
+ }
+ } else {
+ if err := c.client.DeleteResourceWithArgsMap(resourceType, resourceName, deleteArgsMap); err != nil {
+ t.Logf("Ensuring delete failed for resource %s with argsMap %v", resourceName, deleteArgsMap)
+ t.Fatal(err)
+ return
+ } else {
+ t.Logf("Ensuring deletion of %s successful", resourceName)
+ }
+ }
+
+}
+func testHelperVerifyImmutabilityFunc(c *NetScalerNitroClient, t *testing.T, resourceType, resourceName string, resourceInstance interface{}, attribute string) {
+ if _, err := c.client.UpdateResource(resourceType, resourceName, resourceInstance); err != nil {
+ r := regexp.MustCompile(fmt.Sprintf("errorcode.*278.*Invalid argument \\[%s\\]", attribute))
+
+ if r.Match([]byte(err.Error())) {
+ t.Logf("Succesfully verified immutability of attribute \"%s\"", attribute)
+ } else {
+ t.Errorf("Error while assesing immutability of attribute \"%s\"", attribute)
+ t.Fatal(err)
+ }
+ } else {
+ t.Fatalf("Error (no error) while assesing immutability of attribute \"%s\"", attribute)
+ }
+}
diff --git a/citrixadc_framework/provider_test.go b/citrixadc_framework/acctests/provider_test.go
similarity index 57%
rename from citrixadc_framework/provider_test.go
rename to citrixadc_framework/acctests/provider_test.go
index eaae0caef..7f306bf16 100644
--- a/citrixadc_framework/provider_test.go
+++ b/citrixadc_framework/acctests/provider_test.go
@@ -1,4 +1,4 @@
-package citrixadc_framework
+package acctests
import (
"fmt"
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/citrix/adc-nitro-go/service"
+ "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
@@ -15,7 +16,7 @@ import (
// CLI command executed to create a provider server to which the CLI can
// reattach.
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
- "citrixadc": providerserver.NewProtocol6WithError(New("test")()),
+ "citrixadc": providerserver.NewProtocol6WithError(provider.New("test")()),
}
func testAccPreCheck(t *testing.T) {
@@ -55,33 +56,33 @@ func testAccGetFrameworkClient() (*service.NitroClient, error) {
return client, nil
}
-func TestProviderNew(t *testing.T) {
- provider := New("test")()
+// func TestProviderNew(t *testing.T) {
+// provider := provider.New("test")()
- if provider == nil {
- t.Fatal("Provider should not be nil")
- }
+// if provider == nil {
+// t.Fatal("Provider should not be nil")
+// }
- // Test that the provider implements the required interface
- _, ok := provider.(*CitrixAdcFrameworkProvider)
- if !ok {
- t.Fatal("Provider should be of type *CitrixAdcFrameworkProvider")
- }
-}
+// // Test that the provider implements the required interface
+// _, ok := provider.(*(provider.CitrixAdcFrameworkProvider))
+// if !ok {
+// t.Fatal("Provider should be of type *CitrixAdcFrameworkProvider")
+// }
+// }
-func TestProviderModel(t *testing.T) {
- model := CitrixAdcFrameworkProviderModel{}
+// func TestProviderModel(t *testing.T) {
+// model := provider.CitrixAdcFrameworkProviderModel{}
- // Test that all fields are properly defined
- if !model.Username.IsNull() {
- t.Error("New model Username should be null")
- }
+// // Test that all fields are properly defined
+// if !model.Username.IsNull() {
+// t.Error("New model Username should be null")
+// }
- if !model.Password.IsNull() {
- t.Error("New model Password should be null")
- }
+// if !model.Password.IsNull() {
+// t.Error("New model Password should be null")
+// }
- if !model.Endpoint.IsNull() {
- t.Error("New model Endpoint should be null")
- }
-}
+// if !model.Endpoint.IsNull() {
+// t.Error("New model Endpoint should be null")
+// }
+// }
diff --git a/citrixadc/resource_citrixadc_sslcertkey_test.go b/citrixadc_framework/acctests/resource_citrixadc_sslcertkey_test.go
similarity index 72%
rename from citrixadc/resource_citrixadc_sslcertkey_test.go
rename to citrixadc_framework/acctests/resource_citrixadc_sslcertkey_test.go
index 593363a83..74b9ccd70 100644
--- a/citrixadc/resource_citrixadc_sslcertkey_test.go
+++ b/citrixadc_framework/acctests/resource_citrixadc_sslcertkey_test.go
@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
-package citrixadc
+package acctests
import (
"fmt"
@@ -26,12 +26,61 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
+const testAccSslcertkey_basic = `
+
+resource "citrixadc_sslcertkey" "foo" {
+ certkey = "sample_ssl_cert"
+ cert = "/nsconfig/ssl/servercert1.cert"
+ key = "/nsconfig/ssl/servercert1.key"
+ notificationperiod = 40
+ expirymonitor = "ENABLED"
+}
+`
+
+const testAccSslcertkey_basic_update = `
+
+variable "sslcertkey_passplain_wo" {
+ type = string
+ sensitive = true
+}
+
+resource "citrixadc_sslcertkey" "foo" {
+ certkey = "sample_ssl_cert"
+ cert = "/nsconfig/ssl/servercert2.cert"
+ key = "/nsconfig/ssl/servercert2.key"
+ nodomaincheck = true
+ password = true
+ passplain_wo = var.sslcertkey_passplain_wo
+ passplain_wo_version = 2
+}
+`
+
+const testAccSslcertkey_basic_update_2 = `
+
+variable "sslcertkey_passplain_wo_2" {
+ type = string
+ sensitive = true
+}
+
+resource "citrixadc_sslcertkey" "foo" {
+ certkey = "sample_ssl_cert"
+ cert = "/nsconfig/ssl/servercert3.cert"
+ key = "/nsconfig/ssl/servercert3.key"
+ nodomaincheck = true
+ password = true
+ passplain_wo = var.sslcertkey_passplain_wo_2
+ passplain_wo_version = 3
+}
+`
+
func TestAccSslcertkey_basic(t *testing.T) {
- t.Skip("TODO: Need to find a way to test this resource!")
+ // t.Skip("TODO: Need to find a way to test this resource!")
+ t.Setenv("TF_VAR_sslcertkey_passplain_wo", "123456")
+ t.Setenv("TF_VAR_sslcertkey_passplain_wo_2", "1234567")
resource.Test(t, resource.TestCase{
- PreCheck: func() { doSslcertkeyPreChecks(t) },
- ProviderFactories: testAccProviderFactories,
- CheckDestroy: testAccCheckSslcertkeyDestroy,
+ PreCheck: func() { doSslcertkeyPreChecks(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckSslcertkeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccSslcertkey_basic,
@@ -39,127 +88,90 @@ func TestAccSslcertkey_basic(t *testing.T) {
testAccCheckSslcertkeyExist("citrixadc_sslcertkey.foo", nil),
resource.TestCheckResourceAttr(
- "citrixadc_sslcertkey.foo", "cert", "/nsconfig/ssl/certificate1.crt"),
+ "citrixadc_sslcertkey.foo", "cert", "/nsconfig/ssl/servercert1.cert"),
resource.TestCheckResourceAttr(
"citrixadc_sslcertkey.foo", "certkey", "sample_ssl_cert"),
resource.TestCheckResourceAttr(
- "citrixadc_sslcertkey.foo", "key", "/nsconfig/ssl/key1.pem"),
+ "citrixadc_sslcertkey.foo", "key", "/nsconfig/ssl/servercert1.key"),
+ ),
+ },
+ {
+ Config: testAccSslcertkey_basic_update,
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckSslcertkeyExist("citrixadc_sslcertkey.foo", nil),
+
+ resource.TestCheckResourceAttr(
+ "citrixadc_sslcertkey.foo", "cert", "/nsconfig/ssl/servercert2.cert"),
+ resource.TestCheckResourceAttr(
+ "citrixadc_sslcertkey.foo", "certkey", "sample_ssl_cert"),
+ resource.TestCheckResourceAttr(
+ "citrixadc_sslcertkey.foo", "key", "/nsconfig/ssl/servercert2.key"),
+ ),
+ },
+ {
+ Config: testAccSslcertkey_basic_update_2,
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckSslcertkeyExist("citrixadc_sslcertkey.foo", nil),
+
+ resource.TestCheckResourceAttr(
+ "citrixadc_sslcertkey.foo", "cert", "/nsconfig/ssl/servercert3.cert"),
+ resource.TestCheckResourceAttr(
+ "citrixadc_sslcertkey.foo", "certkey", "sample_ssl_cert"),
+ resource.TestCheckResourceAttr(
+ "citrixadc_sslcertkey.foo", "key", "/nsconfig/ssl/servercert3.key"),
),
},
},
})
}
-func testAccCheckSslcertkeyExist(n string, id *string) resource.TestCheckFunc {
- return func(s *terraform.State) error {
- rs, ok := s.RootModule().Resources[n]
- if !ok {
- return fmt.Errorf("Not found: %s", n)
- }
-
- if rs.Primary.ID == "" {
- return fmt.Errorf("No ssl cert name is set")
- }
-
- if id != nil {
- if *id != "" && *id != rs.Primary.ID {
- return fmt.Errorf("Resource ID has changed!")
- }
-
- *id = rs.Primary.ID
- }
-
- // Use the shared utility function to get a configured client
- client, err := testAccGetClient()
- if err != nil {
- return fmt.Errorf("Failed to get test client: %v", err)
- }
- data, err := client.FindResource(service.Sslcertkey.Type(), rs.Primary.ID)
-
- if err != nil {
- return err
- }
-
- if data == nil {
- return fmt.Errorf("SSL cert %s not found", n)
- }
+const testAccSslcertkey_linkcert_nolink = `
- return nil
- }
+resource "citrixadc_sslcertkey" "client" {
+ cert = "/nsconfig/ssl/servercert1.cert"
+ key = "/nsconfig/ssl/servercert1.key"
+ certkey = "client"
}
-func testAccCheckSslcertkeyDestroy(s *terraform.State) error {
- // Use the shared utility function to get a configured client
- client, err := testAccGetClient()
- if err != nil {
- return fmt.Errorf("Failed to get test client: %v", err)
- }
-
- for _, rs := range s.RootModule().Resources {
- if rs.Type != "citrixadc_sslcertkey" {
- continue
- }
-
- if rs.Primary.ID == "" {
- return fmt.Errorf("No name is set")
- }
+resource "citrixadc_sslcertkey" "intermediate" {
+ cert = "/nsconfig/ssl/intermediate1.cert"
+ certkey = "intermediate"
+}
- _, err := client.FindResource(service.Sslcertkey.Type(), rs.Primary.ID)
- if err == nil {
- return fmt.Errorf("SSL certkey %s still exists", rs.Primary.ID)
- }
+`
- }
+// TODO Add use case with cross signed certificate to do a link-unlink operation in one pass
+const testAccSslcertkey_linkcert_linked = `
- return nil
+resource "citrixadc_sslcertkey" "client" {
+ cert = "/nsconfig/ssl/servercert1.cert"
+ key = "/nsconfig/ssl/servercert1.key"
+ certkey = "client"
+ linkcertkeyname = citrixadc_sslcertkey.intermediate.certkey
}
-func doSslcertkeyPreChecks(t *testing.T) {
- testAccPreCheck(t)
-
- uploads := []string{
- "ca.crt",
- "intermediate.crt",
- "certificate1.crt",
- "certificate2.crt",
- "certificate3.crt",
- "key1.pem",
- "key2.pem",
- "key3.pem",
- }
-
- c, err := testHelperInstantiateClient("", "", "", false)
- if err != nil {
- t.Fatalf("Failed to instantiate client. %v\n", err)
- }
-
- //c := testAccProvider.Meta().(*NetScalerNitroClient)
- for _, filename := range uploads {
- err := uploadTestdataFile(c, t, filename, "/var/tmp")
- if err != nil {
- t.Errorf("%v", err)
- }
- }
+resource "citrixadc_sslcertkey" "intermediate" {
+ cert = "/nsconfig/ssl/intermediate1.cert"
+ certkey = "intermediate"
}
-const testAccSslcertkey_basic = `
+`
+const testAccSslcertkey_linkcert_client_key_removed = `
-resource "citrixadc_sslcertkey" "foo" {
- certkey = "sample_ssl_cert"
- cert = "/nsconfig/ssl/certificate1.crt"
- key = "/nsconfig/ssl/key1.pem"
- notificationperiod = 40
- expirymonitor = "ENABLED"
+resource "citrixadc_sslcertkey" "intermediate" {
+ cert = "/nsconfig/ssl/intermediate1.cert"
+ certkey = "intermediate"
}
+
`
func TestAccSslcertkey_linkcert(t *testing.T) {
- t.Skip("TODO: Need to find a way to test this resource!")
+ // t.Skip("TODO: Need to find a way to test this resource!")
resource.Test(t, resource.TestCase{
- PreCheck: func() { doSslcertkeyPreChecks(t) },
- ProviderFactories: testAccProviderFactories,
- CheckDestroy: testAccCheckSslcertkeyDestroy,
+ PreCheck: func() { doSslcertkeyPreChecks(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckSslcertkeyDestroy,
Steps: []resource.TestStep{
// Check initial link
@@ -168,7 +180,6 @@ func TestAccSslcertkey_linkcert(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckSslcertkeyExist("citrixadc_sslcertkey.client", nil),
testAccCheckSslcertkeyExist("citrixadc_sslcertkey.intermediate", nil),
-
resource.TestCheckResourceAttr(
"citrixadc_sslcertkey.client", "linkcertkeyname", "intermediate"),
),
@@ -180,9 +191,7 @@ func TestAccSslcertkey_linkcert(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckSslcertkeyExist("citrixadc_sslcertkey.client", nil),
testAccCheckSslcertkeyExist("citrixadc_sslcertkey.intermediate", nil),
-
- resource.TestCheckResourceAttr(
- "citrixadc_sslcertkey.client", "linkcertkeyname", ""),
+ // resource.TestCheckResourceAttr("citrixadc_sslcertkey.client", "linkcertkeyname", ""),
),
},
@@ -213,8 +222,7 @@ func TestAccSslcertkey_linkcert(t *testing.T) {
testAccCheckSslcertkeyExist("citrixadc_sslcertkey.client", nil),
testAccCheckSslcertkeyExist("citrixadc_sslcertkey.intermediate", nil),
- resource.TestCheckResourceAttr(
- "citrixadc_sslcertkey.client", "linkcertkeyname", ""),
+ // resource.TestCheckResourceAttr("citrixadc_sslcertkey.client", "linkcertkeyname", ""),
),
},
@@ -241,49 +249,8 @@ func TestAccSslcertkey_linkcert(t *testing.T) {
})
}
-const testAccSslcertkey_linkcert_nolink = `
-
-resource "citrixadc_sslcertkey" "client" {
- cert = "/nsconfig/ssl/certificate1.crt"
- key = "/nsconfig/ssl/key1.pem"
- certkey = "client"
-}
-
-resource "citrixadc_sslcertkey" "intermediate" {
- cert = "/nsconfig/ssl/intermediate.crt"
- certkey = "intermediate"
-}
-
-`
-
-// TODO Add use case with cross signed certificate to do a link-unlink operation in one pass
-const testAccSslcertkey_linkcert_linked = `
-
-resource "citrixadc_sslcertkey" "client" {
- cert = "/nsconfig/ssl/certificate1.crt"
- key = "/nsconfig/ssl/key1.pem"
- certkey = "client"
- linkcertkeyname = citrixadc_sslcertkey.intermediate.certkey
-}
-
-resource "citrixadc_sslcertkey" "intermediate" {
- cert = "/nsconfig/ssl/intermediate.crt"
- certkey = "intermediate"
-}
-
-`
-
-const testAccSslcertkey_linkcert_client_key_removed = `
-
-resource "citrixadc_sslcertkey" "intermediate" {
- cert = "/nsconfig/ssl/intermediate.crt"
- certkey = "intermediate"
-}
-
-`
-
func TestAccSslcertkey_AssertNonUpdateableAttributes(t *testing.T) {
- t.Skip("TODO: Need to find a way to test this resource!")
+ // t.Skip("TODO: Need to find a way to test this resource!")
if tfAcc := os.Getenv("TF_ACC"); tfAcc == "" {
t.Skip("TF_ACC not set. Skipping acceptance test.")
@@ -303,8 +270,8 @@ func TestAccSslcertkey_AssertNonUpdateableAttributes(t *testing.T) {
certkeyInstance := ssl.Sslcertkey{
Certkey: certkeyName,
- Cert: "/nsconfig/ssl/certificate1.crt",
- Key: "/nsconfig/ssl/key1.pem",
+ Cert: "/nsconfig/ssl/servercert1.cert",
+ Key: "/nsconfig/ssl/servercert1.key",
}
if _, err := c.client.AddResource(certkeyType, certkeyName, certkeyInstance); err != nil {
@@ -317,7 +284,7 @@ func TestAccSslcertkey_AssertNonUpdateableAttributes(t *testing.T) {
certkeyInstance.Key = ""
//cert
- certkeyInstance.Cert = "/nsconfig/ssl/new/crt"
+ certkeyInstance.Cert = "/nsconfig/ssl/new/cert"
testHelperVerifyImmutabilityFunc(c, t, certkeyType, certkeyName, certkeyInstance, "cert")
certkeyInstance.Cert = ""
@@ -371,3 +338,67 @@ func TestAccSslcertkey_AssertNonUpdateableAttributes(t *testing.T) {
testHelperVerifyImmutabilityFunc(c, t, certkeyType, certkeyName, certkeyInstance, "ocspstaplingcache")
certkeyInstance.Ocspstaplingcache = false
}
+
+func testAccCheckSslcertkeyExist(n string, id *string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ rs, ok := s.RootModule().Resources[n]
+ if !ok {
+ return fmt.Errorf("Not found: %s", n)
+ }
+
+ if rs.Primary.ID == "" {
+ return fmt.Errorf("No ssl cert name is set")
+ }
+
+ if id != nil {
+ if *id != "" && *id != rs.Primary.ID {
+ return fmt.Errorf("Resource ID has changed!")
+ }
+
+ *id = rs.Primary.ID
+ }
+
+ // Use the shared utility function to get a configured client
+ client, err := testAccGetFrameworkClient()
+ if err != nil {
+ return fmt.Errorf("Failed to get test client: %v", err)
+ }
+ data, err := client.FindResource(service.Sslcertkey.Type(), rs.Primary.ID)
+
+ if err != nil {
+ return err
+ }
+
+ if data == nil {
+ return fmt.Errorf("SSL cert %s not found", n)
+ }
+
+ return nil
+ }
+}
+
+func testAccCheckSslcertkeyDestroy(s *terraform.State) error {
+ // Use the shared utility function to get a configured client
+ client, err := testAccGetFrameworkClient()
+ if err != nil {
+ return fmt.Errorf("Failed to get test client: %v", err)
+ }
+
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "citrixadc_sslcertkey" {
+ continue
+ }
+
+ if rs.Primary.ID == "" {
+ return fmt.Errorf("No name is set")
+ }
+
+ _, err := client.FindResource(service.Sslcertkey.Type(), rs.Primary.ID)
+ if err == nil {
+ return fmt.Errorf("SSL certkey %s still exists", rs.Primary.ID)
+ }
+
+ }
+
+ return nil
+}
diff --git a/citrixadc_framework/resource_citrixadc_lbparameter_test.go b/citrixadc_framework/acctests/resource_lbparameter_test.go
similarity index 92%
rename from citrixadc_framework/resource_citrixadc_lbparameter_test.go
rename to citrixadc_framework/acctests/resource_lbparameter_test.go
index 734846201..25b05833a 100644
--- a/citrixadc_framework/resource_citrixadc_lbparameter_test.go
+++ b/citrixadc_framework/acctests/resource_lbparameter_test.go
@@ -1,19 +1,4 @@
-/*
-Copyright 2016 Citrix Systems, Inc
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-package citrixadc_framework
+package acctests
import (
"fmt"
diff --git a/citrixadc_framework/acctests/testdata/.gitignore b/citrixadc_framework/acctests/testdata/.gitignore
new file mode 100644
index 000000000..0529931bd
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/.gitignore
@@ -0,0 +1 @@
+!*.pem
diff --git a/citrixadc_framework/acctests/testdata/appfw_signatures.xml b/citrixadc_framework/acctests/testdata/appfw_signatures.xml
new file mode 100644
index 000000000..6390b0899
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/appfw_signatures.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+child
+descendant
+descendant-or-self
+ancestor
+ancestor-or-self
+following-sibling
+preceding-sibling
+attribute
+following
+preceding
+namespace
+self
+parent
+position
+last
+node
+text
+comment
+processing-instruction
+or
+and
+\|
+true
+false
+count
+id
+local-name
+namespace-uri
+name
+string
+concat
+starts-with
+contains
+substring-before
+substring-after
+substring
+string-length
+normalize-space
+translate
+boolean
+not
+lang
+number
+sum
+floor
+ceiling
+round
+
+<
+>
+=
+'
+"
+\*
+::
+/
+//
+@
+
+
+
+
+
diff --git a/citrixadc_framework/acctests/testdata/appfwhtmlerrorpage.html b/citrixadc_framework/acctests/testdata/appfwhtmlerrorpage.html
new file mode 100644
index 000000000..e3aa44a8d
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/appfwhtmlerrorpage.html
@@ -0,0 +1 @@
+Error Page Example
\ No newline at end of file
diff --git a/citrixadc_framework/acctests/testdata/appfwjsonerrorpage.json b/citrixadc_framework/acctests/testdata/appfwjsonerrorpage.json
new file mode 100644
index 000000000..2765d663c
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/appfwjsonerrorpage.json
@@ -0,0 +1,8 @@
+{
+ "errors": [
+ {
+ "status": 404,
+ "title": "Not found"
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/citrixadc_framework/acctests/testdata/appfwxmlerrorpage.xml b/citrixadc_framework/acctests/testdata/appfwxmlerrorpage.xml
new file mode 100644
index 000000000..09795f2f8
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/appfwxmlerrorpage.xml
@@ -0,0 +1,3 @@
+
+ 404 Error
+
\ No newline at end of file
diff --git a/citrixadc_framework/acctests/testdata/appfwxmlschema.xml b/citrixadc_framework/acctests/testdata/appfwxmlschema.xml
new file mode 100644
index 000000000..d3b3d090f
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/appfwxmlschema.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Prose in the spec does not specify that attributes are allowed on the Body element
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fault reporting structure
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/citrixadc_framework/acctests/testdata/bot_signatures.json b/citrixadc_framework/acctests/testdata/bot_signatures.json
new file mode 100644
index 000000000..2a6cc39db
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/bot_signatures.json
@@ -0,0 +1,20 @@
+{
+ "enable_bot_profiles": "TRUE",
+ "bot_profiles": [
+ {
+ "version": "2.1",
+ "name": "a.pr-cy.ru",
+ "drop": "TRUE",
+ "redirect": "FALSE",
+ "reset": "FALSE",
+ "id": "1",
+ "type": "Bad Bot",
+ "category": "Crawler",
+ "log": "TRUE",
+ "enabled": "TRUE",
+ "developer": "Mirafox"
+ }
+ ],
+ "version": "6.0",
+ "schema_version": "2.0"
+}
\ No newline at end of file
diff --git a/citrixadc_framework/acctests/testdata/ca.crt b/citrixadc_framework/acctests/testdata/ca.crt
new file mode 100644
index 000000000..658f34395
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/ca.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICtzCCAZ+gAwIBAgIJAPncHsX6eYvtMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
+BAMMB1Jvb3QtY2EwHhcNMjAwOTAzMTAyMjM3WhcNMzAwOTAxMTAyMjM3WjASMRAw
+DgYDVQQDDAdSb290LWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+1kSRomP3l51zagNeFgQjLS6cPnMImV3MXA5Rz0XcPx9oFfYZbE6nklZnU+EWYo+g
+dM4INn42HpFgq05qbasufr/y/ECoAg5im7Zqp7/rPdbeUa9UoZANBe+CKQ/nQFFu
+Z0idVekn4CFr5d23k058CgbAPDwMTwQaP5tzmldSOkabrCfnADZP6K1i+7yGE/eB
+bEnS0R98yfqsjqbNuqPim8oncOQg2Sb4uj5YgIOS8JdfoPGkgZvfeeauw0P2iUvd
+9GwP84Hgw7foykItbvaS+rqUH8jgnhkX8e6Q+VGAJhZvyMGkpg98+wAZARHujZzo
+ALNsAywJ9BYX8gUVFtLHrwIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4IBAQB+HAu3hkD2AotYwFkEduAsuElE9EBmaXlrrPewwup2qfhMjxKY
+3/iuwrSC/Jy+OFRcvCTmjMIEK6ybS2U395aUBVZpcZYL/UyaPF3tmaI6E6JUr5ed
+rmtfKbwar5Xrwb3/txrjZxh+FCEKPtm+vzVR3bN7wsWEyUiKI7y+/PKVVNcPEg/L
+WuQOdkky5Yq+rkzUkS2ONX/OCtdfvBfc74xbYAvsLFR03eOoCvf6X6baaKVjupmU
+kTxT5sKiNP0y9xSBvuTrC3sHVCJ190TfPIwp2OTcAaVtYAC/j3w3pQwlghF56zQ7
+bAZnmAg1C4n65GJ+9Jomvqwx8zlcrs3XYbQ7
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/certificate1.crt b/citrixadc_framework/acctests/testdata/certificate1.crt
new file mode 100644
index 000000000..445b24a78
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/certificate1.crt
@@ -0,0 +1,66 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 4097 (0x1001)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN=Interm.
+ Validity
+ Not Before: Sep 3 10:22:38 2020 GMT
+ Not After : Sep 3 10:22:38 2021 GMT
+ Subject: CN=1.example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e2:6b:e7:3e:d8:65:d9:c0:06:6c:29:65:d6:11:
+ f3:8d:5b:39:92:69:94:9a:95:e6:e2:7c:03:7d:99:
+ e3:3a:98:ed:76:c9:b4:49:1b:d7:8b:4a:26:d2:54:
+ c4:fe:73:6f:b5:98:0a:ee:ed:fd:20:3c:55:52:38:
+ 4f:38:d4:73:7a:7e:be:cc:ec:60:27:3d:fe:1a:26:
+ 52:8e:23:c6:2c:08:37:ae:6f:13:d1:5d:96:18:a9:
+ 4c:e2:5e:98:fd:10:25:2d:26:82:78:34:01:95:bc:
+ 06:c1:55:fc:dd:9b:df:82:62:ba:98:3c:5e:14:fe:
+ 89:d5:f9:1a:13:18:6e:d2:8f:9d:a7:0e:a9:a0:b3:
+ 5b:d8:b4:ba:11:21:c6:b8:89:32:ff:3d:c0:07:56:
+ 5e:ff:eb:93:d1:63:6b:d5:bf:2a:d3:51:69:27:ba:
+ c3:74:45:18:48:80:07:b2:e8:26:90:f5:3e:29:eb:
+ 54:a5:b9:ec:dd:79:bc:42:45:04:39:5a:4c:a3:ad:
+ 59:e9:c5:a9:35:7e:1d:5a:31:d1:a5:2a:2d:ea:dc:
+ bb:a8:55:1f:28:0a:08:13:2a:b3:99:98:22:00:3a:
+ 3e:fb:e0:44:3b:21:8c:a4:f0:78:98:70:58:1e:29:
+ 8f:46:c1:64:81:01:65:38:a4:85:c4:95:29:7b:41:
+ d6:99
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha256WithRSAEncryption
+ 4d:1a:09:c0:cf:05:f2:61:0d:cb:2d:1d:a9:55:5f:8e:77:5d:
+ cc:96:3a:5d:ea:8a:4d:b9:52:26:70:b0:4d:f8:c3:17:c4:4b:
+ 0d:65:8d:29:a2:da:3b:6a:62:ff:11:1f:7d:4a:6d:a4:95:79:
+ 46:5f:45:46:0c:72:13:79:ea:44:fe:24:5f:d0:19:4b:f8:c4:
+ 08:9b:ad:18:94:75:10:22:f0:b0:4f:c1:a5:34:58:98:ac:17:
+ a6:41:f7:e9:a4:c3:43:0a:96:06:10:5d:fa:25:2e:e4:d9:f1:
+ 6b:9d:06:93:9c:2a:66:86:b2:b0:f2:07:c7:9f:98:c5:cf:0b:
+ 76:ca:e2:f4:a6:ff:f7:18:f9:98:44:82:cb:75:c6:86:ef:ab:
+ 64:4c:72:a8:6d:45:5c:19:d4:0a:88:65:d2:0f:85:cf:66:4f:
+ 49:5e:7e:49:d6:5c:10:13:50:0f:68:e6:fe:d7:50:65:1b:59:
+ 4d:53:3f:2a:22:bf:72:43:5d:59:c2:35:5e:24:ad:1b:86:fb:
+ b4:30:02:35:c0:a3:d3:bd:78:9b:77:95:9c:97:f4:15:86:92:
+ 34:f2:6a:d9:d5:ba:64:c1:f7:de:2a:14:1f:9b:6f:10:ed:e2:
+ 64:1a:81:7c:86:68:37:bf:93:c3:10:36:e1:da:1a:0d:66:fb:
+ 7c:25:6e:7a
+-----BEGIN CERTIFICATE-----
+MIICnzCCAYcCAhABMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNVBAMMB0ludGVybS4w
+HhcNMjAwOTAzMTAyMjM4WhcNMjEwOTAzMTAyMjM4WjAYMRYwFAYDVQQDDA0xLmV4
+YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4mvnPthl
+2cAGbCll1hHzjVs5kmmUmpXm4nwDfZnjOpjtdsm0SRvXi0om0lTE/nNvtZgK7u39
+IDxVUjhPONRzen6+zOxgJz3+GiZSjiPGLAg3rm8T0V2WGKlM4l6Y/RAlLSaCeDQB
+lbwGwVX83ZvfgmK6mDxeFP6J1fkaExhu0o+dpw6poLNb2LS6ESHGuIky/z3AB1Ze
+/+uT0WNr1b8q01FpJ7rDdEUYSIAHsugmkPU+KetUpbns3Xm8QkUEOVpMo61Z6cWp
+NX4dWjHRpSot6ty7qFUfKAoIEyqzmZgiADo+++BEOyGMpPB4mHBYHimPRsFkgQFl
+OKSFxJUpe0HWmQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBNGgnAzwXyYQ3LLR2p
+VV+Od13Mljpd6opNuVImcLBN+MMXxEsNZY0poto7amL/ER99Sm2klXlGX0VGDHIT
+eepE/iRf0BlL+MQIm60YlHUQIvCwT8GlNFiYrBemQffppMNDCpYGEF36JS7k2fFr
+nQaTnCpmhrKw8gfHn5jFzwt2yuL0pv/3GPmYRILLdcaG76tkTHKobUVcGdQKiGXS
+D4XPZk9JXn5J1lwQE1APaOb+11BlG1lNUz8qIr9yQ11ZwjVeJK0bhvu0MAI1wKPT
+vXibd5Wcl/QVhpI08mrZ1bpkwffeKhQfm28Q7eJkGoF8hmg3v5PDEDbh2hoNZvt8
+JW56
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/certificate2.crt b/citrixadc_framework/acctests/testdata/certificate2.crt
new file mode 100644
index 000000000..2e5c94d2c
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/certificate2.crt
@@ -0,0 +1,66 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 4098 (0x1002)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN=Interm.
+ Validity
+ Not Before: Sep 3 10:22:38 2020 GMT
+ Not After : Sep 3 10:22:38 2021 GMT
+ Subject: CN=2.example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bb:a9:bb:ef:8f:04:6d:e1:95:ef:96:34:ff:c2:
+ df:3c:03:77:25:f1:2b:70:bf:10:b0:aa:dd:b9:1b:
+ a5:bf:65:66:1e:d5:52:92:3e:ff:f2:dd:56:61:7f:
+ a8:1c:bc:f3:7c:be:ac:c0:4c:2b:41:f6:3e:0f:ff:
+ f7:92:06:e6:e7:67:7d:f5:5a:22:a8:cd:88:a5:79:
+ cd:cd:c0:9e:19:34:10:98:13:cb:6c:db:d7:c9:52:
+ 7a:4e:85:de:a0:22:f0:b6:c5:ff:1e:4b:f0:04:d5:
+ 84:af:9f:3d:04:85:8d:99:31:d0:50:34:c3:c1:e5:
+ 30:82:29:d9:cd:c5:7a:8a:d7:5d:db:70:7b:1f:46:
+ ce:43:32:53:bc:8f:3e:d6:9e:ef:aa:2c:58:0c:d1:
+ 51:10:6e:a2:fb:1f:07:f4:af:73:b8:ff:ce:74:77:
+ 07:4c:f3:60:ae:09:76:c7:4b:47:8a:05:a3:0a:f6:
+ 9c:4f:fa:de:a7:29:b5:19:ff:c6:e3:86:bd:fc:b0:
+ 06:5f:cf:73:04:00:28:92:8d:de:8e:d1:9b:04:4a:
+ bf:dd:9d:8f:95:df:90:bb:ac:04:df:c0:1d:3f:f1:
+ 1b:7a:0b:4c:1d:1e:89:63:6c:dd:7c:1f:02:4e:7a:
+ b6:42:0a:21:24:b4:a4:bc:32:c8:2e:57:43:42:94:
+ e3:01
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha256WithRSAEncryption
+ 6f:07:6c:71:5b:69:9a:a3:f2:9f:e7:e7:05:e6:a3:8f:06:09:
+ 63:2e:20:67:40:92:38:4e:03:9f:bb:46:bf:eb:15:ac:04:1b:
+ b0:42:ec:58:c5:cd:54:3d:43:1d:42:4b:80:73:24:73:d2:7f:
+ 9f:1b:f1:25:9a:b5:77:8a:9e:23:8b:fb:f0:9f:03:6a:b2:ea:
+ ef:39:7c:30:5b:6d:ff:08:7e:e6:04:6a:e6:61:97:7e:f1:3e:
+ 2a:f4:2f:8c:18:c6:5b:00:d7:60:55:07:a4:56:93:b0:7e:10:
+ 54:a8:df:d9:c9:07:cb:85:22:86:42:d2:dc:6c:c8:32:47:87:
+ 27:27:5e:16:42:fd:42:62:6d:bf:7c:13:b6:a9:32:49:ee:f7:
+ 57:33:ef:a0:f3:d9:25:6c:b7:21:30:51:33:c8:b0:75:0c:62:
+ 1e:b6:f0:76:fe:75:b3:b3:25:15:eb:a8:78:9a:f4:23:f4:7b:
+ cf:ee:44:09:46:6e:3f:2d:86:2c:93:fe:2b:c2:b8:78:a7:88:
+ c2:b9:c7:6e:84:8e:f5:d5:5d:e4:44:2d:2d:83:a3:28:8c:9f:
+ 06:ed:43:7a:9e:4e:6e:ef:0c:d8:74:07:ef:56:c4:35:77:cd:
+ 95:43:c3:fa:c7:c2:f1:cb:fa:1a:0c:5a:1e:d0:0d:20:96:87:
+ e9:eb:08:c6
+-----BEGIN CERTIFICATE-----
+MIICnzCCAYcCAhACMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNVBAMMB0ludGVybS4w
+HhcNMjAwOTAzMTAyMjM4WhcNMjEwOTAzMTAyMjM4WjAYMRYwFAYDVQQDDA0yLmV4
+YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6m7748E
+beGV75Y0/8LfPAN3JfErcL8QsKrduRulv2VmHtVSkj7/8t1WYX+oHLzzfL6swEwr
+QfY+D//3kgbm52d99VoiqM2IpXnNzcCeGTQQmBPLbNvXyVJ6ToXeoCLwtsX/Hkvw
+BNWEr589BIWNmTHQUDTDweUwginZzcV6itdd23B7H0bOQzJTvI8+1p7vqixYDNFR
+EG6i+x8H9K9zuP/OdHcHTPNgrgl2x0tHigWjCvacT/repym1Gf/G44a9/LAGX89z
+BAAoko3ejtGbBEq/3Z2Pld+Qu6wE38AdP/EbegtMHR6JY2zdfB8CTnq2QgohJLSk
+vDLILldDQpTjAQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBvB2xxW2mao/Kf5+cF
+5qOPBgljLiBnQJI4TgOfu0a/6xWsBBuwQuxYxc1UPUMdQkuAcyRz0n+fG/ElmrV3
+ip4ji/vwnwNqsurvOXwwW23/CH7mBGrmYZd+8T4q9C+MGMZbANdgVQekVpOwfhBU
+qN/ZyQfLhSKGQtLcbMgyR4cnJ14WQv1CYm2/fBO2qTJJ7vdXM++g89klbLchMFEz
+yLB1DGIetvB2/nWzsyUV66h4mvQj9HvP7kQJRm4/LYYsk/4rwrh4p4jCucduhI71
+1V3kRC0tg6MojJ8G7UN6nk5u7wzYdAfvVsQ1d82VQ8P6x8Lxy/oaDFoe0A0glofp
+6wjG
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/certificate3.crt b/citrixadc_framework/acctests/testdata/certificate3.crt
new file mode 100644
index 000000000..decfe9b29
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/certificate3.crt
@@ -0,0 +1,66 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 4099 (0x1003)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN=Interm.
+ Validity
+ Not Before: Sep 3 10:22:38 2020 GMT
+ Not After : Sep 3 10:22:38 2021 GMT
+ Subject: CN=3.example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ad:72:8b:7e:be:a5:25:f6:25:07:db:e8:09:5c:
+ 88:c4:ba:12:92:4f:de:6b:52:7c:3b:2d:31:03:de:
+ c9:e4:25:bf:98:7e:00:34:dd:b3:4c:28:67:4f:1d:
+ 20:68:1c:d5:32:d3:6a:b2:09:ac:f5:97:b8:69:14:
+ 78:3c:52:47:cb:23:7b:8f:ca:f7:98:8d:86:8d:35:
+ e8:c4:93:d7:50:30:b8:f1:d0:5a:15:06:b9:72:71:
+ 68:9c:c5:30:b0:35:c0:ec:b3:bd:41:5a:48:2c:4c:
+ bd:c1:5b:86:f8:79:12:a8:70:d0:4c:aa:44:c8:c5:
+ 6b:c8:93:9c:03:1d:45:c4:cd:75:88:1b:b1:e0:fd:
+ eb:b3:44:86:15:8b:b1:7e:30:2c:b2:1a:bb:1c:e7:
+ fa:7c:05:75:dc:4d:5d:ae:7a:49:3e:2f:fb:56:68:
+ af:ab:28:d7:e7:fe:6a:74:a1:ee:fa:bc:4d:af:11:
+ d4:8c:b6:c4:4d:ef:f7:33:25:a0:0b:97:bd:2e:29:
+ b0:4b:ba:dd:6f:85:25:4c:e2:a6:f9:15:28:4f:02:
+ bc:eb:f5:51:cb:e6:fd:1a:66:65:a5:89:ec:c5:aa:
+ d3:a8:73:05:3a:a5:ea:bb:f4:56:e7:b6:8d:5e:3c:
+ 1b:9a:55:28:4b:b1:f9:d4:ec:ef:81:4a:dc:66:93:
+ f1:fd
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha256WithRSAEncryption
+ 62:b8:52:51:98:86:83:0a:50:70:8f:d9:93:d8:aa:71:b2:ad:
+ 70:bf:2f:eb:4f:51:57:d4:0b:16:65:18:c3:17:c1:09:de:e7:
+ d1:9a:07:e3:e7:b2:14:d9:59:aa:e0:5e:1a:89:02:e0:f7:55:
+ 31:6e:7a:a3:66:16:71:2d:75:75:fe:4a:95:b7:c3:6c:90:11:
+ 67:8e:79:f8:eb:3a:92:8a:7a:4b:78:dc:11:2f:44:55:b3:a0:
+ f3:9a:c5:d1:20:34:5a:7f:27:ec:b6:8b:8c:ff:bb:3e:63:0e:
+ 98:b1:bf:7e:b6:c1:e0:0f:5a:96:92:e6:a4:2f:7f:df:f6:d5:
+ 63:47:f4:ca:8e:ee:66:08:5d:14:db:59:00:7e:9d:2d:69:e9:
+ 65:48:94:41:71:b5:57:98:7d:e3:e0:e4:d2:01:ce:d1:98:5d:
+ 4b:71:26:bd:54:1d:14:b5:87:50:b2:08:2c:24:e0:cf:ce:73:
+ 78:0d:92:de:e2:01:16:d1:ce:4a:e7:c9:09:b6:64:fd:85:65:
+ c8:d1:1f:67:64:4d:7e:c8:98:4c:a3:f0:ad:b4:31:70:cd:ab:
+ ee:aa:25:56:dd:4c:66:96:5a:29:4a:fd:a7:92:ff:ad:d1:24:
+ c3:74:a6:e9:02:30:27:81:16:1a:38:bf:b3:f7:24:d3:db:66:
+ 39:5d:86:84
+-----BEGIN CERTIFICATE-----
+MIICnzCCAYcCAhADMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNVBAMMB0ludGVybS4w
+HhcNMjAwOTAzMTAyMjM4WhcNMjEwOTAzMTAyMjM4WjAYMRYwFAYDVQQDDA0zLmV4
+YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArXKLfr6l
+JfYlB9voCVyIxLoSkk/ea1J8Oy0xA97J5CW/mH4ANN2zTChnTx0gaBzVMtNqsgms
+9Ze4aRR4PFJHyyN7j8r3mI2GjTXoxJPXUDC48dBaFQa5cnFonMUwsDXA7LO9QVpI
+LEy9wVuG+HkSqHDQTKpEyMVryJOcAx1FxM11iBux4P3rs0SGFYuxfjAsshq7HOf6
+fAV13E1drnpJPi/7VmivqyjX5/5qdKHu+rxNrxHUjLbETe/3MyWgC5e9LimwS7rd
+b4UlTOKm+RUoTwK86/VRy+b9GmZlpYnsxarTqHMFOqXqu/RW57aNXjwbmlUoS7H5
+1OzvgUrcZpPx/QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBiuFJRmIaDClBwj9mT
+2Kpxsq1wvy/rT1FX1AsWZRjDF8EJ3ufRmgfj57IU2Vmq4F4aiQLg91UxbnqjZhZx
+LXV1/kqVt8NskBFnjnn46zqSinpLeNwRL0RVs6DzmsXRIDRafyfstouM/7s+Yw6Y
+sb9+tsHgD1qWkuakL3/f9tVjR/TKju5mCF0U21kAfp0taellSJRBcbVXmH3j4OTS
+Ac7RmF1LcSa9VB0UtYdQsggsJODPznN4DZLe4gEW0c5K58kJtmT9hWXI0R9nZE1+
+yJhMo/CttDFwzavuqiVW3UxmllopSv2nkv+t0STDdKbpAjAngRYaOL+z9yTT22Y5
+XYaE
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/error.html b/citrixadc_framework/acctests/testdata/error.html
new file mode 100644
index 000000000..74fcfd566
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/error.html
@@ -0,0 +1 @@
+Hello error
diff --git a/citrixadc_framework/acctests/testdata/intermediate.crt b/citrixadc_framework/acctests/testdata/intermediate.crt
new file mode 100644
index 000000000..6837801fc
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/intermediate.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsDCCAZigAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UEAwwHUm9v
+dC1jYTAeFw0xOTEwMTEwODAyNTdaFw0yMDEwMTAwODAyNTdaMBIxEDAOBgNVBAMM
+B0ludGVybS4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRpHw7QpBX
+RaOvFW2D7ET3KrD+IWQbzrRVx4K/UCIQP/HE+2oA22zTrPqt6dKKMr40lUaMDpQC
+gYFHsk88s4W6DRk+dMEPIfGv0bvv1HdVyKWlSa0NMR1vA84dhtau3/N9XGCWomim
+BgwCP2k6pqtFMikwVRJ2M+MZIHckG5NAGzaMxi/doYrr61eDTl1oMrhLHkRxCFhZ
+23FYyJqp7ZLrNhuSw9w8PkKB0R0UbMj5RxUvapXsKNx9eKpVlAULzYlJEGveRGy1
+73yYFXWx2tCJ5isFC+3GIc1q3iuhI9IpPYvqwiqJprqBiOiLrCOSc3mY3W5/BHdS
+rTnTNRhGHZffAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
+ggEBADE2gMg14ok9nBEchXvKRgnnC2dcqEq8tg8FqDy/n3RbBs16pkSqJCjbuVun
+sI7zmx8ZaQzudCaOnWEu5M5MDxUjvLOEfIIpqiI7zQaGtLt5yuFbYwLu0oj+YzDK
+EYL6DqONVNvZLvm+dwpGU3DfwdXDFWQhHkaSV+pCAJaZT2HcV6XrtxEc8mObpqnf
+A7VNZce/E9rZWcW0csbOAxHwoOFXvhdeFiggmuF9qBKlRSCCnjHK1TBGG/pZYsYd
+6yDEsUIM68vdJtO0NZ0vka6O9SIBB+Z8wGKzlHVy6C2XvdQjXWzyw5tYtAYHtzZv
+O1vBO5yfDfJBqqDuzKytUxXNDhE=
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/intermediate1.cert b/citrixadc_framework/acctests/testdata/intermediate1.cert
new file mode 100644
index 000000000..5e5254495
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/intermediate1.cert
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCFEiDqvyA7irlqvwOMaAZL67wvDe/MA0GCSqGSIb3DQEBBQUAMDIx
+CzAJBgNVBAYTAmluMQ8wDQYDVQQKDAZjaXRyaXgxEjAQBgNVBAMMCXJvb3RjZXJ0
+MTAgFw0yNTExMjIyMTEwNTlaGA8yMDU1MTExNTIxMTA1OVowNjELMAkGA1UEBhMC
+aW4xDzANBgNVBAoMBmNpdHJpeDEWMBQGA1UEAwwNaW50ZXJtZWRpYXRlMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMK/ltiSf9AC2j7zcyYZyw+ZoiY/
+0MykZZOJvV6J4+lrzIncRAnF/3aKQskkrNWZWOZsppietezNkiHt6eHul696RI48
+wqy3Io0vwzzVDe3smC6NbtRYMTci04jFBfS9DErfnmNG3tDv+WfqzTPVNYTjG0wq
+7az2XSzPKoVSYOr0zCd7nbEuD/GsywqJJxmEatTHlD/5SCI+U0P/J72RkBHdWEdv
+58lFBSzG66iku9uz7HKD5xE3I0dReaQpaFj0OsE2fzGwx/+BU44FHa2iiOF6VyAJ
+97ukbt9bwYxOdAQ20MnoqXa2YhnW5LgYmv/4eBrGkjAmxCRDTdEWN++xqZMCAwEA
+ATANBgkqhkiG9w0BAQUFAAOCAQEAm7CxBKRO7V3VxvvSUtOqBG9H20oaJ5f6ltr0
+lgL7cB6Z07J956zYXSmHKMvKNHgPewqE+W9ErqjEggEJwB3Cck+tEBYSyGis7Gqy
+bYAz8tU3n/QXLnBCr8PUp65bQVHYiDPTqiiJOUjITV7zDB9/l1j655DRqcphTPTL
+bMPkCKbDeU4d2GoIlaCA80UmLIoiRjHb6ybtp+5BkAqxuYilWcE3kXdBgx/5qekd
+71KzdwuwKERLYSp5KFvFZMG4IoAbUySVRelm2aqqcmRmEuLBGfyvE/NkdW9bXzDn
+ZDXkQDb/Z8ig9bAT9i2dKHXUlEpGit4JliuV7A2XS4bW3VgB3g==
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/key1.pem b/citrixadc_framework/acctests/testdata/key1.pem
new file mode 100644
index 000000000..4944b7110
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/key1.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4mvnPthl2cAGbCll1hHzjVs5kmmUmpXm4nwDfZnjOpjtdsm0
+SRvXi0om0lTE/nNvtZgK7u39IDxVUjhPONRzen6+zOxgJz3+GiZSjiPGLAg3rm8T
+0V2WGKlM4l6Y/RAlLSaCeDQBlbwGwVX83ZvfgmK6mDxeFP6J1fkaExhu0o+dpw6p
+oLNb2LS6ESHGuIky/z3AB1Ze/+uT0WNr1b8q01FpJ7rDdEUYSIAHsugmkPU+KetU
+pbns3Xm8QkUEOVpMo61Z6cWpNX4dWjHRpSot6ty7qFUfKAoIEyqzmZgiADo+++BE
+OyGMpPB4mHBYHimPRsFkgQFlOKSFxJUpe0HWmQIDAQABAoIBAQCEd2iVBfmgiY24
+XgfsVBhPjR2DoS2Fu0mXG8LXCt87+xiCtMM7OoTCQyGvUFToIRUsAsXyv12mLGYm
+cjc3ImRlOB3cujYO/1/YYK9P5XIddLlO3kGT7dLQnswSbBHJjFaTscIHHTYIKjcx
+iWMlK/p5x9UvF7JbSgfOHay5m0PK/40Joo+HxrUJkMz4A00cFQQKdU6pOKhcX/+K
+4zdimg5uThwxH/MQWvqrxG7ZtqUOg5NGm9dg+9bfw5Jp7qMeHu/GXpe6vmitAd6a
+FHdRQ2m2DhGMisk88RIan664HtXgQVpR4Jt2X+qEcVQLc+dKFsmuD3fwOmvL2V5Q
+ibi4PmQBAoGBAPbp5OR2lOp23ubeaizHmrD0iKk9ACaT3bsk5TqUCLGUEo/SaLNT
+8o7qn/v1QF9gxSQPj08wMt9RBFCqYzmgtUXMrC7u7HR9YcFZ42jtbNbIv1wUXAE6
+Dv9pBCfWynmsuJtRLEMiHb0l3SNbZ2Td9w2WbTM/4KyDMk0s/RgfxJZBAoGBAOrA
+9U9RT1MboxCUJ+j/fHRzk6hhTX1wf5eRzr0pnQqqDnqwUuBVAG6x3IDIh0wWgUCq
+qc45Nwoo1qwzaqT5cZ3OS64FsMnr+N5OFVOspr1fCO6JWtbdNN/OEqq+DW8Si/iZ
+qGr4mMNNX/O9QXGkSAHdB1s5+zYcFjbexJfk5xpZAoGAH/1/zWXbt2D9UjYg1Xpq
+/WBBUIP5wAXKZZPLK0LAuZkwqnedXxaSR5f4cGF/HJxiDmEBtUXOYYaSo4zf3DiJ
+I+j3qgEEm7ce8jkeMJsKTe2mdVyh6vrFtKu7gRngE9Gf/WeP74a9CaOdOhZ+l9/2
+QUlrDofJKTC6VKtugzCifMECgYEA1UiA2BKgxnpKmepxpEBTO54yXn4hIEHQus3P
+jo+7TZAZ3aBLe+Peo7PXCe6m9htQTYeBBYt4FTPrbsK8Nq0na9+dZLto1twc3SUG
+PWKUj2NDwy1qKeMKgfhBf31yySKJp1E78gxxBqhK8DFXvz8p6P9/CoRQV+YGzM1y
+wipHSekCgYEA0XfisC8r09ldD4U7EPbTzudsJV+eMnI1xR5d+XAgg6uGqyc0ndeg
+WaKUWaT9BkAdqrk/FkA39oP9BuerhwBbPwiGMUxRkGrmlIO6MN3lHkqgWQ5SqmRV
+ai3HlWliz/i3E/xc7k54zt+rKNXwoTyeudlRHHlSxK+GOTFuLBb0uLA=
+-----END RSA PRIVATE KEY-----
diff --git a/citrixadc_framework/acctests/testdata/key2.pem b/citrixadc_framework/acctests/testdata/key2.pem
new file mode 100644
index 000000000..fe6ca51ab
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/key2.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAu6m7748EbeGV75Y0/8LfPAN3JfErcL8QsKrduRulv2VmHtVS
+kj7/8t1WYX+oHLzzfL6swEwrQfY+D//3kgbm52d99VoiqM2IpXnNzcCeGTQQmBPL
+bNvXyVJ6ToXeoCLwtsX/HkvwBNWEr589BIWNmTHQUDTDweUwginZzcV6itdd23B7
+H0bOQzJTvI8+1p7vqixYDNFREG6i+x8H9K9zuP/OdHcHTPNgrgl2x0tHigWjCvac
+T/repym1Gf/G44a9/LAGX89zBAAoko3ejtGbBEq/3Z2Pld+Qu6wE38AdP/EbegtM
+HR6JY2zdfB8CTnq2QgohJLSkvDLILldDQpTjAQIDAQABAoIBAEVvqzsbZt8lXe31
+XQzZVPIGsjpWvuULvSgxQLLyOOTVpvB3rAKyzs9U+FZA/roLa+hXQOIyDLtuWk5f
+PoJIjX1HvMJgpHxi+FGJ/Q1JXPYkXpN2l4li2rgCDYEqZVJJO1nVbu0N2fRf8KVu
+vQwEQn1Rgit7Kod+vvEafR13GT1vimwO94Nt1fye4HaQJkWW7UUS+7p6f22wQj+e
+5zb73W3Mk72jh6LG1x48vL4iY4kgIKrtOVG1F9dtwVBkNr80I6LeH8kgbOvTnrNw
+omyKGg/fMFssM08CN5WcOsIEHOiyyyL408O7vUuNk06Vcp8qmKtpbcSqmnXdGsUe
+IdoNX8ECgYEA4CORF1AMmFln7oEG8GSZBTZYDTIBRzAVEd3fi33qIkcT955SuJTE
++iyVVDtjvZXPMfxO1kLln8ih36m9S66pjzi3Hvi7V2Xo5uTsnjWtjSx5vsyu38B2
+icQvz2R4h6N9Xk+pXrxrILrIZstg0auzRiSOR5OKSCup27p70dgUsMkCgYEA1lbO
+YwjdnAx45Ln4A2MNDc7sk24f4hhZJS3qhirKS/gqbUCWKc74FcZBi5zLYvCdwYXu
+WHKYl3hEk7KbEJM0KiQz/vFCOJADMULiFYw4pQrpClTcljr5lG8m/OHVQF0/6mp5
+rEwxi5FwcIX6guP8+ZRd3Z63h7+4I5cTq7CAtHkCgYBBZd9FwBOMuDl8+6S8q32C
+adLTNs3sqXjcV7KMDtcr3TVUQJu+Q5odrLh9dT6q6HUmDooqNiatsmqYyfvzgyjy
+Iwg7PzPaUl/cTttDZkIXOOzk4O/9VTjBBb81cglA+lDwHao7fBp92EH0zE6ZntGW
+G8Bv3fqxCBxtgkHyfmu7EQKBgDQmxuzd2V4AwuGURj48uY5kjLeKkgNnPTmIpImk
+m7hEV75heqgNjdtuc8BOlEYsmZXeypGGwI4KW1U8nfI4fvbJ/ETJ2vz8PWqdBXmM
+trOhpfY3k7yR+Owe53OcV1Dj34tgAN7lYyC8cIlQcBWs936alQQ5fBpxkZJHTif2
+ODqxAoGBAIVKy9l9KRnGrR0WAJcN1+ZyHBz3hCG7tbMD3emHDhFObEuZv2+70viz
+VbkRgSxygb2iQPFJWIXVRiByKX5z+QEMU3En2BkQPP5ZDZ0uIiH/H57hPxVwzE0j
+k3w/KRcsawsLf/45yfSF7HTgkwRDHhynTN1V25YEZ9W6N8NBTSjk
+-----END RSA PRIVATE KEY-----
diff --git a/citrixadc_framework/acctests/testdata/key3.pem b/citrixadc_framework/acctests/testdata/key3.pem
new file mode 100644
index 000000000..2048214df
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/key3.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArXKLfr6lJfYlB9voCVyIxLoSkk/ea1J8Oy0xA97J5CW/mH4A
+NN2zTChnTx0gaBzVMtNqsgms9Ze4aRR4PFJHyyN7j8r3mI2GjTXoxJPXUDC48dBa
+FQa5cnFonMUwsDXA7LO9QVpILEy9wVuG+HkSqHDQTKpEyMVryJOcAx1FxM11iBux
+4P3rs0SGFYuxfjAsshq7HOf6fAV13E1drnpJPi/7VmivqyjX5/5qdKHu+rxNrxHU
+jLbETe/3MyWgC5e9LimwS7rdb4UlTOKm+RUoTwK86/VRy+b9GmZlpYnsxarTqHMF
+OqXqu/RW57aNXjwbmlUoS7H51OzvgUrcZpPx/QIDAQABAoIBAHGgls5DqwGBjaTm
+Zc6sNlVa6Qu9EyMP+J7z7iZw76ZtLGWENjJsAygjV/q2RCKwsOMJKd7VA+AoswD0
+DgjGho4IAsyi1S21ma4s4P7XM7kvEhooursHmrnknfoGHO1zaZ4n6hZERP/wjmd7
+xKJG/vgmX++5pDI7U28ldJF9vdU9CGh6wmprzZgGa1NoZlLWeW0uIXBAjs/WKlko
+woqyygsGFo3SuVVYZRKd9+OOoLRexWiCk4cMLt730Z1SnhIB39/EaDWZqDDkTqNx
+qnIEkWVZGxpHAJ6/T3Bery6ncOqr72msim4bSV3iU3Bqc07Ql8hWDerAA8zWr63I
+9/I3QCkCgYEA0u1U9SJUa4l0KhzKKfQbJWvzsBCd6weS9rPpCSR2xy9v2Gf9q3+M
+wxSQLlcV8vjh8GUfQ57VadmlgPC0kp4kDB+2s1wSg/iRjhAfe7JTd5k4/An4dIvt
+RUuItMwsiEL5V2gA4KULXL7rvBji+lH+NDEBdP8wjeP+374OBzfaZF8CgYEA0oLo
+KJP68DPOpVlXKFUeAa7iCi/l8Pi0aBkrLJSqvmaRSNeiFJEz6VorC3aN1M2KN0Z9
+QcV9t9WSvNOYQSTNrjVcFGeywvDD6pKcVD3as3ouu+nhtH2kOXxIXXix3Kg0hRQq
+5pGul533lFcJpTDIDgUbnUGSCs38Z5Pd1mpqZyMCgYEAiuyQYWh8C9wbq8UMjnde
+Dda9SUCYkn2JmX3DxibDKMwgsXtEw9kdwDth/3OSXFb6kVg5MFOEItScQoHHnS4V
+dfrJXfcNpuhoDhamddVtTj+YHcD/aNvkqhhm8RXtWs4p5hz1PwDVq/9/yoLltJOe
+h4ejewi9VSdO7tUB7lUmPacCgYEAq9gXIoDieVEhYNNUleUd8KvNdBlzsMmlo9Df
+8K2P0Iw0D22PrxB1ewmTV/E4iL4dFVBikd1g6j/bYG+uu4cKrCp8919Li1014Xg7
+S32O2bJlEhszl7504ER3Ym5Ta1iYPwaemsfT6YsXfy2p/wKaXO+Igk/zowRSBk+r
+6QvHvlcCgYAaH2dzg0OMWO4CQlMlfPL8CQulcjM+OmQ6FIurPhT+4oHpbZ72WsDg
+T/OQoh/+URME351w2DH0lCqKLjEhC4HDr/gMFtvlEzvjwREmByZisHc8fVT4O7YE
+5O4Ju1080QdeMQXECDFdPV0AbPka8JvRqjU1xWfy0/OK7bnLkyBy6Q==
+-----END RSA PRIVATE KEY-----
diff --git a/citrixadc_framework/acctests/testdata/other_error.html b/citrixadc_framework/acctests/testdata/other_error.html
new file mode 100644
index 000000000..3b08922fb
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/other_error.html
@@ -0,0 +1 @@
+Hello other error
diff --git a/citrixadc_framework/acctests/testdata/rootcert1.cert b/citrixadc_framework/acctests/testdata/rootcert1.cert
new file mode 100644
index 000000000..ac758f863
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/rootcert1.cert
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC7TCCAdUCFHiVFXos6wufZid3TDdeTw0CnB2rMA0GCSqGSIb3DQEBBQUAMDIx
+CzAJBgNVBAYTAmluMQ8wDQYDVQQKDAZjaXRyaXgxEjAQBgNVBAMMCXJvb3RjZXJ0
+MTAgFw0yNTExMjIyMTEwNTlaGA8yMDU1MTExNTIxMTA1OVowMjELMAkGA1UEBhMC
+aW4xDzANBgNVBAoMBmNpdHJpeDESMBAGA1UEAwwJcm9vdGNlcnQxMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApBHacLFruY2CUyk7sQ/JilIX5Z4lZjJS
+V1x54+cEXghUCMpGMoX2AHNX8SP5Cd2loYY8vuDe57ODYJrbSlJHGvOVvtVnjxeQ
+EdNN0b0Ot0bwpnqTZBgEiSHJXcEXyH2rzY0LnR3ke/eXlGMIGhyavWrPXDDs5csL
+0jnHFxiCjkpIWseuB8/KBZxoJFjnmbV9+p4rAZsZrnALpd6OLQ/kUTGy6BGP79oq
+gy9Vae0II0C6PRTH7VlcMrKswJKyE0tUIgqomHDLYvnHqGl+cIubDib0mXh1Vtly
++I/oJKs8ytC2x8ndc/w9+Nd4BtNctYRSGBFWCDenD/O8r1JzcwRGXwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4IBAQASTUo9b1oOQr8LPMRaBdM5YfzSg00Wg5TaHPeSZMMv
+9mF/E15xiKqM8tuUEJ9PBrgLO0nW+mIAInpVpEvHD+NkmxyhH4WfBGRbVFaSzdgJ
+ewPf9nVQL/zRb4CtOUtqg/ojG0BkUsReaW456mH4zmf3FOfDOKkKw97j1uUymMW4
+pUwa5Iib0OIZUhpqr0b+UTiFEOOLnCL5T2N1gaA998Yl8CYgfxCHojmySgihMu6t
+KBAqtPCOC0Z7Q3maIMET3/+7tjsk/9vZMY1KyT2tDQ8rMPMF/HmztMKnQj5zKgbj
+rBTeR1Nb6mKQxfG7Dp63amtf+BncSC19aD7Q3kVOFkcp
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/sample.wsdl b/citrixadc_framework/acctests/testdata/sample.wsdl
new file mode 100644
index 000000000..49875b13b
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/sample.wsdl
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WSDL File for HelloService
+
+
+
+
+
diff --git a/citrixadc_framework/acctests/testdata/scripts/cachain/generate_all.sh b/citrixadc_framework/acctests/testdata/scripts/cachain/generate_all.sh
new file mode 100644
index 000000000..0e2754f3f
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/scripts/cachain/generate_all.sh
@@ -0,0 +1,91 @@
+#!/bin/bash -x
+
+# Create a full CA chain with self signed CA root
+# Intermediate CA
+# and 3 client certificates
+set -e
+
+for C in `echo root-ca intermediate out`; do
+
+ if [[ -d $C ]]; then
+ echo "Deleting directory $C"
+ rm -rf $C
+ fi
+done
+
+
+for C in `echo root-ca intermediate`; do
+
+ mkdir $C
+ cd $C
+ mkdir certs crl newcerts private
+ cd ..
+
+ echo 1000 > $C/serial
+ touch $C/index.txt $C/index.txt.attr
+
+ echo '
+[ ca ]
+default_ca = CA_default
+[ CA_default ]
+dir = '$C' # Where everything is kept
+certs = $dir/certs # Where the issued certs are kept
+crl_dir = $dir/crl # Where the issued crl are kept
+database = $dir/index.txt # database index file.
+new_certs_dir = $dir/newcerts # default place for new certs.
+certificate = $dir/cacert.pem # The CA certificate
+serial = $dir/serial # The current serial number
+crl = $dir/crl.pem # The current CRL
+private_key = $dir/private/ca.key.pem # The private key
+RANDFILE = $dir/.rnd # private random number file
+nameopt = default_ca
+certopt = default_ca
+policy = policy_match
+default_days = 365
+default_md = sha256
+
+[ policy_match ]
+countryName = optional
+stateOrProvinceName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[req]
+req_extensions = v3_req
+distinguished_name = req_distinguished_name
+
+[req_distinguished_name]
+
+[v3_req]
+basicConstraints = CA:TRUE
+' > $C/openssl.conf
+done
+
+openssl genrsa -out root-ca/private/ca.key 2048
+openssl req -config root-ca/openssl.conf -new -x509 -days 3650 -key root-ca/private/ca.key -sha256 -extensions v3_req -out root-ca/certs/ca.crt -subj '/CN=Root-ca'
+
+# Copy root ca certificate to top level testdata directory
+cp root-ca/certs/ca.crt ../../
+
+openssl genrsa -out intermediate/private/intermediate.key 2048
+openssl req -config intermediate/openssl.conf -sha256 -new -key intermediate/private/intermediate.key -out intermediate/certs/intermediate.csr -subj '/CN=Interm.'
+openssl ca -batch -config root-ca/openssl.conf -keyfile root-ca/private/ca.key -cert root-ca/certs/ca.crt -extensions v3_req -notext -md sha256 -in intermediate/certs/intermediate.csr -out intermediate/certs/intermediate.crt
+
+# Copy intermediate certificate to top level testdata directory
+cp intermediate/certs/intermediate.crt ../../
+
+mkdir out
+
+for I in `seq 1 3` ; do
+ openssl genrsa -out out/key${I}.pem 2048
+ openssl req -new -out out/$I.request -days 365 -key out/key${I}.pem -nodes -subj "/CN=$I.example.com"
+ openssl ca -batch -config root-ca/openssl.conf -keyfile intermediate/private/intermediate.key -cert intermediate/certs/intermediate.crt -out out/certificate${I}.crt -infiles out/$I.request
+
+ # Copy to root of testdata
+ cp out/key${I}.pem ../../
+ cp out/certificate${I}.crt ../../
+done
+
+echo All Good!
diff --git a/citrixadc_framework/acctests/testdata/servercert1.cert b/citrixadc_framework/acctests/testdata/servercert1.cert
new file mode 100644
index 000000000..eeb2b48b3
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/servercert1.cert
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8zCCAdsCFHcoCAiHk5Mxj6ShrrQZ1492naXiMA0GCSqGSIb3DQEBBQUAMDYx
+CzAJBgNVBAYTAmluMQ8wDQYDVQQKDAZjaXRyaXgxFjAUBgNVBAMMDWludGVybWVk
+aWF0ZTEwIBcNMjUxMTIyMjExMTAwWhgPMjA1NTExMTUyMTExMDBaMDQxCzAJBgNV
+BAYTAmluMQ8wDQYDVQQKDAZjaXRyaXgxFDASBgNVBAMMC3NlcnZlcmNlcnQxMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnHx1rgBn68nvwECTp8y3zpKN
+satFnh7gKKyKMx2sE+HbD2zVSlqJaMr52e0JbimqWRjBbJEW5f6N5hZjjKyy397i
+53gwWmZYLtvCJAs5OZizWiPja5HkEjwJTRqblsBLTj6rlH1XOjQsWJhq8areFV1B
+LEPfaR2yg8ITsqSvRx8GhpL+RuPJ8ta2yRVMlG2V5TQFlCvHXdZHgL2Xe3VgDavc
+cngDtSyPYwu1roKQ06qsVJpH+Z362XQg1JKQd8VpNnUO3ZOdMaX3gnHLX1UD8Qh5
+oZGd0t55RVBkhvtOwPXImb9+qRw7kxJxoiw1hN0HUgL4qcmbnGIhE1lCn/Sm7wID
+AQABMA0GCSqGSIb3DQEBBQUAA4IBAQA2Z/7FnDUowdgAA8Uv0lKrao+JooBZ4ujF
++42d068qlSvyoveC6gyoHTGmw0XK5ctO69kapscfl5y1dcSk8YDvmCfF/+FLOzkW
+KxFwZ4iYsSWKwhVcr3IJTqrjR/l+hNFVJtsypYcMJfANvFyf942GzLqAmgquqiKO
+WqTrsB1Lj4ci9t3kc5gL2nxLZyXC8c7iqL3LgMaRl5ICsCBOrE30pX8Xm4vDNezf
+eY3NrqKDBPFVTOsHVQ6v20/QNGgKidK9p76M1CDVUubqyi7KcJA/ZiMzHIkMu4Y9
+TEUAnVTBVeSnmL1rqqvPotJtNW8rkuMnpB+/ov2eZPlaczq6i119
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/servercert1.key b/citrixadc_framework/acctests/testdata/servercert1.key
new file mode 100644
index 000000000..43bef3324
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/servercert1.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCcfHWuAGfrye/A
+QJOnzLfOko2xq0WeHuAorIozHawT4dsPbNVKWoloyvnZ7QluKapZGMFskRbl/o3m
+FmOMrLLf3uLneDBaZlgu28IkCzk5mLNaI+NrkeQSPAlNGpuWwEtOPquUfVc6NCxY
+mGrxqt4VXUEsQ99pHbKDwhOypK9HHwaGkv5G48ny1rbJFUyUbZXlNAWUK8dd1keA
+vZd7dWANq9xyeAO1LI9jC7WugpDTqqxUmkf5nfrZdCDUkpB3xWk2dQ7dk50xpfeC
+cctfVQPxCHmhkZ3S3nlFUGSG+07A9ciZv36pHDuTEnGiLDWE3QdSAvipyZucYiET
+WUKf9KbvAgMBAAECggEAQi2CSO1EBZvXR/snVpHFc9dXk/kGDoopKxpYsahNQvVD
+goD4rKKgNNCOHELZt5xoPZiyCBtNz0M5OEJkpDPOy81DMTeLX1ej2GE4d6zFbR2u
+T6MYxL+kb6+zEUf836J/1l27iyb8shEpZehDZeNoWymea71iaF5WRdFYKjbqJ02G
+iRcF4D0FRhxKg0rlqJTFxmy3E8jL8R0zr3hyarI3SgraYPp22rhuDZIhfdKN/P6X
+w9k79Vw9N3KYhxVDJrIuPj4L/slrolxrmlpKPbksT4E7mM+a8lriF1IOzmEExWOy
+YAAOOw8VViqXg1NjyHPLkP2zeWuSEU1i9DwjTj354QKBgQDUin4Zy8hMpSazILmj
+2fP1w5Gp10vb8fNisXUGO6OyzkAvkm7wuL8gBK4nUFTE93t3MaJgV8ZfnlUMgm5M
+t0S71EFn/bV3RpAUm1lzEPzUzQJgtcyiG3srv0xyR5A1t3QkLtTLvx1FFz5CZQLH
+iKmlYIJDKQaQjCcnVTkza+FEzwKBgQC8e8PETWoSFlAZYuAwFoUAeRnvz7y5xTQ2
+BGqZ4APCISoZmhxCpn7puQDqfZoMNQlCwbSyGGl59Dew2V53tXYy5EZoz1lD0vyF
+zawLLeymatT/qayg2JKFeRUoYBvShmMTVubHx1nMLpB/C3p9b1z/l8Zgj6MfCye8
+3MbCLftD4QKBgHxW2Ra2ROOx6kX46yGULEimtNyre1Gc5knijxeqZEYCq0IpZIWn
+TwioNEoDkNP6BFziyJ+cOg1OT2sWEvkGbkuEDQ+NOVAiE8A6ccYDNiY4GSACu0hK
+02/wZgSlIRSL0oIoc40OrUzyIBYvicS5iqWZJBuuMIz3sSAtl10hy5O5AoGAGdCZ
+r3kq3e9QaSmxquRqsvXjJ4U8Q/VOgVd5gjm8SgpgycDhvf8vwrMj/PnW73UUH6CI
+LxxI1fss5XvgBGVGGxJI0nITt8Zd77WLqrxPfTuEkL+cdSs0ZjN/Qlhndx4Q08VD
+NnmHQv/dqojX7fYitp8C/JavsMDGYIeccVv63gECgYAyUUjeRtiOEyFsT87YNJio
+lHJHvbWkpHysg1kmH0nllUbNOFvpaxgQFgrkAOIkm/yr1NlfdO3vvm07q5YSkdG4
+37TxjV3/oG+gCmq3OO+G3j1MoKmVbDIcGDLDQxFQpY8Ysv2W8Zv9RJdsRlgKl9Cf
+xyecK6zOH2G9paxAsMVy/w==
+-----END PRIVATE KEY-----
diff --git a/citrixadc_framework/acctests/testdata/servercert2.cert b/citrixadc_framework/acctests/testdata/servercert2.cert
new file mode 100644
index 000000000..7d19599a7
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/servercert2.cert
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCFE9/930cXTbXGmmYYI2w2hrnH0x2MA0GCSqGSIb3DQEBBQUAMDQx
+CzAJBgNVBAYTAmluMQ8wDQYDVQQKDAZjaXRyaXgxFDASBgNVBAMMC3NlcnZlcmNl
+cnQyMCAXDTI1MTEyMzE4MTg0OFoYDzIwNTUxMTE2MTgxODQ4WjA0MQswCQYDVQQG
+EwJpbjEPMA0GA1UECgwGY2l0cml4MRQwEgYDVQQDDAtzZXJ2ZXJjZXJ0MjCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALovNXgxect5cycg1SJvz1JWl9yb
+N8O1u4mCCivV2OUX58VD4O50SfH5OqnzHDs73igx/nOyNlhVlDiSdUCyCeyHqY1s
+yQ4tkg81tZ5MgKkMvCteDVFxr6CbvnzMv4IAFM+blAlhatMlP8QrOoZPHwFoDRhG
+6N796280elDtAiEPBWm2yol2ppizIs8sOY8V3YcV4kfvt9/uWmoQatT8oK6vkOWD
+fGsQtHoFhDNM36aw9OHmOBYuD0aJsvpSbUpLWoXQCmm37sSiJIAOLT+TQ9o4QtXq
+dXRqgkBVH5uFYRiffT03oPBs7mhcUmFszHdoypcDPjcopeF73R8vtb0rLw8CAwEA
+ATANBgkqhkiG9w0BAQUFAAOCAQEAT9CP3hK2SjHRV4Y9qzn+w5y58cI46lyoPm4M
+AzMMwUtH26NfRF88hY165Rcx+GZE8m6vIhS/iye/SYXejUFV73T3V6C8T4lTWNYT
+gyEwFOw5OcaxmUuG7nJ0iC0MuEFPoUECgve0dWzo2oCBx7ph5bFh8crpw4XY/uFW
+bb1kfPWulZc9RTFwL4JmOloBcN/Hogg5BSWxySVHowqtnD/DHLWtwCLlFC0Icwes
+Zno8kYhG+Ldmty6WRz/cwnZloanCbu5Pg3Tr1Hkz/n8VoZaY0JVLUlHDxtKW1rRT
+/t+QqcRTrxG39TIN3r1l34FQj56feZLLEQxqLxKeSScduxD47g==
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/servercert2.key b/citrixadc_framework/acctests/testdata/servercert2.key
new file mode 100644
index 000000000..6c6737be9
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/servercert2.key
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIuCRrPSbI2HICAggA
+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECOB3ebE4qYugBIIEyOSrC2YvMdGU
+/0owhm4N4JweVzEO9ybGANZloTn8l+k3b8A1rt7C56LbVy3U8g1XCQIi4xSLOIqY
+09L3HH0Ar64qGTcdwRpykHQV6cUF01BfvLKp7IPFogzpF+YIqn3WE2iNye7DeC5J
+lFNvhPbqg27Xbluv7pLyCPurASqCDitcXQm+iR1Neupb8HBk56nrLBE74Yr5O7Hv
+mPq3XC1ivMAYzm1B5O0OOiM6yH5cdGuUfLNMnBZs9g+ZqnzdpQmvdr0Lfq1WmPie
+FZoIbccyQ8sq+O0AmjlOuOqLUuzxd0+SWVGc59TMYQi+fuPqVnvlwGUiUGPG4h7p
+mdpRgquMiNr4ag2VpZX2Sc8W8M2/+y3HYsmCoPdEtVEuom/YDidnD72w1SJfdls1
+W0jIppYaOF++9Ik7kWLOiPkiI4ag2cn/r4nIz+k1Cjv8obK1tjQmXMzRvH5x2FIv
+UY6DllzdR9e2OmryllLzkqWKn1jzSVRAMc9cbrrFxMpX7Xxli6jipZSOkWjshd1j
+4gb/tckdLHgSVoGSCvWeaNB7fyVtMGkgEUKy01WTn5iPsHeMouP0V8eVdRXHs6kN
+Z9h/N0W+vBBJ2sAtVYLU2rbiBHvE7ulLps/xvGYTBAnVF7QXt3jW1tB3j1rqlK/W
+D9MgXSXV/pfbcVgIQ5+Y/tk1HstiDSYQzdXcRlLZrN9rr2xGrmJ9ONcfIEXYY1bC
+Od7tLYjDkTOhHquW+E15nVCd4u+C/1wzhK5GEnO7FzFXm74cx8qjIfFseArVJkEi
+ISWhSdgOsKYpedx55cjG44JH/mmsRzmLgpi39vqKzQSYgGpGF+fK5aiGrd3GKSe9
+K/qzxQ0RjpgdiCk8OoYAGk1AFwEkRgqNOWQEOCisVh8WSBtCe2znG+W82YZvOzo5
+eOZvvGkUAp0+LSnueR777rzS9QPxY/Wfy+wTBrvEjorD4vTDGX1LSadTjIM3rGZs
++fVILuLwAx5+FtKm/68h+9e7l659576DfEb67LFDEVuMPrTwSfHP6Ava5njoLUaZ
+TBgpx62nTLLcwYVClYzxtvizT01hdDKkR/sPguPxnjXvct3IZZXq4spHR4oUJM56
+mg1Aw4qINJp8Ty4Mt3KAXc/QPx8aRRK35J/4n73uiIYwEuHdW9FGlM9+zFYaIlG8
+jqVDdhFsO8Q++ues9Vp5kWQHe79sn2/lEkHyw+kHSJV+UhhTQHRM+98R6sMg2A8S
+8T5FSzwBUgPeslRpA08bemluFttsECP021vwBp6P2np1d7Q2iRfhf+gtaaGJjzjM
+WAnc0/1ew54ElEwslCuyEvGtbaKDCZMZuukORnlFeUEWEYcEhoUvLecIOQ7Pqvub
+ClK24KZ/d4LPGoKp7tvVVAdtET1vFNVGuk/QH+ATxhyo3IK0fp9Y3WiSX4BdvEN/
+qMOGmJ6ZE1PXMyhAXTcGoUbcrIT/98mreO1PV/x6DfzaNBdaUO8oGAS2xfTcaJ7+
+bgZWCNORt1BUjntGwOhX4rzRg6/wx/vHeYZyCGq9kCQpUFcnCV1dJbc4joiXfgdL
+Vy+Qo4phNPTKAcv9/izLRcwJdYrSmrmZwTmKFdvurCN5la8Oua3abCsHW8BBdcpt
+Tdk69FAVX8LFDnmUIdluPA==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/citrixadc_framework/acctests/testdata/servercert3.cert b/citrixadc_framework/acctests/testdata/servercert3.cert
new file mode 100644
index 000000000..c23b90b0f
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/servercert3.cert
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8TCCAdkCFGEcxOmTxr9+1WkbiVdloefWdAEgMA0GCSqGSIb3DQEBBQUAMDQx
+CzAJBgNVBAYTAmluMQ8wDQYDVQQKDAZjaXRyaXgxFDASBgNVBAMMC3NlcnZlcmNl
+cnQzMCAXDTI1MTEyMzE4MzkwOVoYDzIwNTUxMTE2MTgzOTA5WjA0MQswCQYDVQQG
+EwJpbjEPMA0GA1UECgwGY2l0cml4MRQwEgYDVQQDDAtzZXJ2ZXJjZXJ0MzCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK/JEhuTkHevuRirWKYzVH4L+PbJ
+n2h9DLkeVZqQKpakTSHjlO94eZSXDsp3939XkxiVX2jJ/dhRhVyHt5r48Hxsf4FP
+mu6YAKvRc7BMOinqm8jE8i/PcVCYdnU9wr8txZ3iBvjn/NYDglgKqOduarntGmsK
+WgqBuby2Mq3Clcicd4DgNBONLEFKmL5cgkqoo6p9yc3AgVs0UEPtUX/Li+/ikola
+p6WBSGWldJ2KxmQrw8+E4H0aBb6QEc97I1229yeg22ZTwudH5pmL5KRwncoiFzLr
+61lgpIt3LUvyUhFDducomJbPCvWXfccYYtOaxT2zLihaW3S8C3dhJ0HBgLkCAwEA
+ATANBgkqhkiG9w0BAQUFAAOCAQEAI2PNl+KdL0sZiKTpkCDlHmWMqFxo71B5Sjk7
+8IR88E0+I5Pgl+nRbD1sVApwqwLsUlk//nwQx7sIzrhZQ/YgSojJ+DSHipyfPery
+GnjOZzRtXDPcTe2Ki4UN8Bb81K5NuEIk+bh8bNMoRd5gcIxLYt+y94cKqYfTDHd2
+jhhQhfBBSGItrWt4gcNkVUgKd31bQNXpy5iJF8L7Bc4NqzM4BMjUOh/LDZKqvUQ4
+MAkaZDqsTgtXuc5dlFSQtitM51f+Nl44NEUvL5BoImSxn1MBPDdnSWX7Pn2NweIY
+JFH6LlLsuYQWjjI9lK5vWIA/xxPiknYAtCuB0Z/cdOQE8ppz0A==
+-----END CERTIFICATE-----
diff --git a/citrixadc_framework/acctests/testdata/servercert3.key b/citrixadc_framework/acctests/testdata/servercert3.key
new file mode 100644
index 000000000..e5585a76c
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/servercert3.key
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIn+Tht6MNwdUCAggA
+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECBbfGZHYWKRiBIIEyBwKHPLu/TOE
+p0B3X0qHe2zWy5vlscJFRaNocXbCYdOB+WOLP5o+NGvjcKKSlD91uQ/YEHedEUUU
+NyyV8MY1x0etzYhlkYwXKt8voXCBdnlbw9t+BQWB5nDk4h71fjI/OzEXa5xAZb9U
+4ZCz8Pnwxy+9/5riRx2yTJtOab+nY3FxcWRPbG3ney49cnJGb//ZWSdhEpOWvX35
+wLb2DRHSB1DWyPVLUN3AtDKEIkHR5P0Gmp1GPwwTpRmiprosYyWyZc3P3roL1bVQ
+isYbVwXO7BQjqF793XN4ce2Q9saKrNdtaufWPQhs2b6H+084jnRcowfdtq9j7nNE
+ilyyJQd1ZX700vRTZ3DfSy2oIIimjXm0syXsFRf4DeMSbvb2/TXdWaOEPrAX8MnP
+RUbp44dYi61NIP5nL/wu6/wpmG6W+AHRly/AO3DSDSs4vi9AlTZvyOL4ItEFFU1f
+NgFKiQdZRGRkjC6AaYp5JSMubG2s4plewibHS+//33a13UW8n0Z5MQe8F09Jy3Nw
+vgCez8uSTC3s98iQVgbDuj0hd15NXhO9AHM8ppLs8nUYyon2yHBhuPAonjK1s6rf
+iMLvp2KIOVrdeDDICHNO3ToDT/TweCJTbS4tsOoemQUBcVn6vpnk3rj/xdxkS1AL
+APxlowRAv2eAENPCcGxoYvwjrIcSf799I7E32lJfZkKa2EBrOvThYb97QhugmiYz
+hhsXxLV/tBBuFhUYRi0g/KVKOPWLeT0ACZeMtDNx9T1RpMbvXQFuLDWXbWGzulta
+74qFUdz57Ni2Fv0pOOk2fVpyh/UztuvH6Sl9z45OKFSUrMA00S8bsol6T8ZD4nMw
+wtfykFMhygenNrvjArCKG/rHS8gAcQYjNK07WN2SRcAubZd9nC48/aCIycLrWKGo
+OFHu6auWCBvbBTotucLnnlICuea9T31aRoWU6jNgZfT0fmOgyik12HW3QTvdodzv
+oB+bBUwqMQbJR9b4dS6K4yQrmiz+W6hdhQbLljlVluMpbNMRhDR32JfgTrwtXhVx
+jWle37C68DvBpp0k3BEwq5xy9AmQZlTTI3rAPXvrGgtpvoR7NbA7Pah76CQjhqwe
+v3VePwzDqBDPYK1XPyC1FqOGT4NK5duu+AsHPXmbZFBdKoWSHgdiEyXfsjNiwWqQ
+MHq8piT3DCL+rfct38Mcfp3QA/vGcEMaCiYE0c84K3w9AgbsWxQcMsnthx3S+Oj1
+NwoJloJ2II1Xm2zzc101eiRx5L7ubNhYpivFDlEqvCnDPb0Q+sxMh0HfLbThfZKz
+c4ufEdkr5RjmmkJG5I2SKWX9epWTsUkQrOubEoyE3t6wLIkxsIMyIxBulTTrhvz7
+N7/PKSvfR9a/HSspJWoy2nMVbQysA8PHIG7aeo2Tj63pc7Rj1uGsHMr45czSK+zP
+5bjDJAx/EpsiKORPiGqtD0EyXho9JPX/RyF4dMdub2nIOQGZ3W+kEBV7uO6lMnzT
+D2pkXJNKUfiv7clhIz77wmi4U6uVS4cmIxUdpEAFCOaQCiRXKnJgVXPqtvo/ay2m
+arUo5S1biCkQ78vsblb9dyuNRPy3CMsnvg30ltMDagQi98kKCkJjZYUFUXu61Nkt
+9GIFLKkKDb0P/fM5//DAcg==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/citrixadc_framework/acctests/testdata/workflows.yaml b/citrixadc_framework/acctests/testdata/workflows.yaml
new file mode 100644
index 000000000..f3650d535
--- /dev/null
+++ b/citrixadc_framework/acctests/testdata/workflows.yaml
@@ -0,0 +1,654 @@
+workflow:
+
+ server:
+ lifecycle: object
+ endpoint: server
+ primary_id_attribute: name
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - domain
+ - state
+ - ipv6address
+ - td
+ - querytype
+ - delay
+ - graceful
+ - Internal
+ - newname
+
+ service:
+ lifecycle: object
+ endpoint: service
+ primary_id_attribute: name
+ resource_missing_errorcode: 344
+ allow_recreate: true
+ non_updateable_attributes:
+ - ip
+ - servername
+ - servicetype
+ - port
+ - cleartextport
+ - cachetype
+ - state
+ - td
+ - delay
+ - graceful
+ - all
+ - Internal
+ - newname
+
+ servicegroup:
+ lifecycle: object
+ endpoint: servicegroup
+ primary_id_attribute: servicegroupname
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - servicetype
+ - cachetype
+ - td
+ - state
+ - memberport
+ - delay
+ - graceful
+ - includemembers
+ - newname
+
+ service_lbmonitor_binding:
+ lifecycle: binding
+ endpoint: service_lbmonitor_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: monitor_name
+ delete_id_attributes:
+ - monitor_name
+
+ servicegroup_lbmonitor_binding:
+ lifecycle: binding
+ endpoint: servicegroup_lbmonitor_binding
+ bound_resource_missing_errorcode: 351
+ primary_id_attribute: servicegroupname
+ secondary_id_attribute: monitor_name
+ delete_id_attributes:
+ - monitor_name
+ - port
+
+ lbgroup:
+ lifecycle: object
+ endpoint: lbgroup
+ primary_id_attribute: name
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - newname
+
+ lbgroup_lbvserver_binding:
+ lifecycle: binding
+ endpoint: lbgroup_lbvserver_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: vservername
+ delete_id_attributes:
+ - vservername
+
+ lbvserver:
+ lifecycle: object
+ endpoint: lbvserver
+ primary_id_attribute: name
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - servicetype
+ - port
+ - range
+ - state
+ - td
+ - redirurlflags
+ - newname
+
+ lbvserver_analyticsprofile_binding:
+ lifecycle: binding
+ endpoint: lbvserver_analyticsprofile_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: analyticsprofile
+ delete_id_attributes:
+ - analyticsprofile
+
+ lbvserver_appflowpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_appflowpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_appfwpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_appfwpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_appqoepolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_appqoepolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_auditnslogpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_auditnslogpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_auditsyslogpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_auditsyslogpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_authorizationpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_authorizationpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_cachepolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_cachepolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_cmppolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_cmppolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_csvserver_binding:
+ lifecycle: binding
+ endpoint: lbvserver_csvserver_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ delete_id_attributes: []
+
+ lbvserver_dnspolicy64_binding:
+ lifecycle: binding
+ endpoint: lbvserver_dnspolicy64_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_feopolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_feopolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_filterpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_filterpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_pqpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_pqpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_responderpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_responderpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_rewritepolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_rewritepolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_scpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_scpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_servicegroupmember_binding:
+ lifecycle: binding
+ endpoint: lbvserver_servicegroupmember_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ delete_id_attributes: []
+
+ lbvserver_servicegroup_binding:
+ lifecycle: binding
+ endpoint: lbvserver_servicegroup_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: servicegroupname
+ delete_id_attributes:
+ - servicegroupname
+ - servicename
+
+ lbvserver_service_binding:
+ lifecycle: binding
+ endpoint: lbvserver_service_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: servicename
+ delete_id_attributes:
+ - servicename
+ - servicegroupname
+
+ lbvserver_spilloverpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_spilloverpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - bindpoint
+ - priority
+
+ lbvserver_transformpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_transformpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_contentinspectionpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_contentinspectionpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_videooptimizationdetectionpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_videooptimizationdetectionpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbvserver_videooptimizationpacingpolicy_binding:
+ lifecycle: binding
+ endpoint: lbvserver_videooptimizationpacingpolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ lbmetrictable:
+ lifecycle: object
+ endpoint: lbmetrictable
+ primary_id_attribute: metrictable
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes: []
+
+ lbmetrictable_metric_binding:
+ lifecycle: binding
+ endpoint: lbmetrictable_metric_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: metrictable
+ secondary_id_attribute: metric
+ delete_id_attributes:
+ - metric
+
+ lbmonitor:
+ lifecycle: object
+ endpoint: lbmonitor
+ primary_id_attribute: monitorname
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - servicename
+ - servicegroupname
+ delete_id_attributes:
+ - type
+ - respcode
+
+ lbmonitor_metric_binding:
+ lifecycle: binding
+ endpoint: lbmonitor_metric_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: monitorname
+ secondary_id_attribute: metric
+ delete_id_attributes:
+ - metric
+
+ lbmonitor_sslcertkey_binding:
+ lifecycle: binding
+ endpoint: lbmonitor_sslcertkey_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: monitorname
+ secondary_id_attribute: certkeyname
+ delete_id_attributes:
+ - certkeyname
+ - ca
+
+ lbprofile:
+ lifecycle: object
+ endpoint: lbprofile
+ primary_id_attribute: lbprofilename
+ resource_missing_errorcode: 3574
+ allow_recreate: true
+ non_updateable_attributes: []
+
+ lbroute:
+ lifecycle: non_updateable_object
+ endpoint: lbroute
+ primary_id_attribute: network
+ resource_missing_errorcode: 258
+ delete_id_attributes:
+ - netmask
+ - td
+
+ lbroute6:
+ lifecycle: non_updateable_object
+ endpoint: lbroute6
+ primary_id_attribute: network
+ resource_missing_errorcode: 258
+ delete_id_attributes:
+ - td
+
+ csvserver_rewritepolicy_binding:
+ lifecycle: binding
+ endpoint: csvserver_rewritepolicy_binding
+ bound_resource_missing_errorcode: 258
+ primary_id_attribute: name
+ secondary_id_attribute: policyname
+ delete_id_attributes:
+ - policyname
+ - priority
+ - bindpoint
+
+ spilloverpolicy:
+ lifecycle: object
+ endpoint: spilloverpolicy
+ primary_id_attribute: name
+ resource_missing_errorcode: 2054
+ allow_recreate: true
+ non_updateable_attributes:
+ - newname
+
+ rewriteaction:
+ lifecycle: object
+ endpoint: rewriteaction
+ primary_id_attribute: name
+ resource_missing_errorcode: 538
+ allow_recreate: true
+ non_updateable_attributes:
+ - type
+ - newname
+
+ rewritepolicy:
+ lifecycle: object
+ endpoint: rewritepolicy
+ primary_id_attribute: name
+ resource_missing_errorcode: 2054
+ allow_recreate: true
+ non_updateable_attributes:
+ - newname
+
+ sslvserver_sslcertkey_binding:
+ lifecycle: binding
+ endpoint: sslvserver_sslcertkey_binding
+ bound_resource_missing_errorcode: 461
+ primary_id_attribute: vservername
+ secondary_id_attribute: certkeyname
+ delete_id_attributes:
+ - certkeyname
+ - crlcheck
+ - ocspcheck
+ - ca
+ - snicert
+
+ sslvserver_sslcipher_binding:
+ lifecycle: binding
+ endpoint: sslvserver_sslcipher_binding
+ bound_resource_missing_errorcode: 461
+ primary_id_attribute: vservername
+ secondary_id_attribute: ciphername
+ delete_id_attributes:
+ - ciphername
+
+ sslvserver:
+ lifecycle: object
+ endpoint: sslvserver
+ primary_id_attribute: vservername
+ resource_missing_errorcode: 461
+ allow_recreate: true
+ non_updateable_attributes: []
+
+ sslprofile:
+ lifecycle: object
+ endpoint: sslprofile
+ primary_id_attribute: name
+ resource_missing_errorcode: 3248
+ allow_recreate: true
+ non_updateable_attributes:
+ - sslprofiletype
+
+ sslprofile_sslcipher_binding:
+ lifecycle: binding
+ endpoint: sslprofile_sslcipher_binding
+ bound_resource_missing_errorcode: 3248
+ primary_id_attribute: name
+ secondary_id_attribute: ciphername
+ delete_id_attributes:
+ - ciphername
+
+ sslcipher:
+ lifecycle: object
+ endpoint: sslcipher
+ primary_id_attribute: ciphergroupname
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - ciphgrpalias
+ - sslprofile
+
+ sslparameter:
+ lifecycle: parameter_object
+ endpoint: sslparameter
+
+ policypatset:
+ lifecycle: non_updateable_object
+ endpoint: policypatset
+ primary_id_attribute: name
+ resource_missing_errorcode: 2823
+ delete_id_attributes: []
+
+ policypatset_pattern_binding:
+ lifecycle: binding
+ endpoint: policypatset_pattern_binding
+ bound_resource_missing_errorcode: 2823
+ primary_id_attribute: name
+ secondary_id_attribute: String
+ delete_id_attributes:
+ - String
+
+ transformprofile:
+ lifecycle: object
+ endpoint: transformprofile
+ primary_id_attribute: name
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes: []
+
+ transformaction:
+ lifecycle: object
+ endpoint: transformaction
+ primary_id_attribute: name
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - profilename
+
+ transformpolicy:
+ lifecycle: object
+ endpoint: transformpolicy
+ primary_id_attribute: name
+ resource_missing_errorcode: 2054
+ allow_recreate: true
+ non_updateable_attributes:
+ - newname
+
+ dnssoarec:
+ lifecycle: object
+ endpoint: dnssoarec
+ primary_id_attribute: domain
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - ecssubnet
+ - type
+ - nodeid
+
+ ntpserver:
+ lifecycle: object_by_args
+ endpoint: ntpserver
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes: []
+ delete_id_attributes:
+ - servername
+ - serverip
+
+ ntpparam:
+ lifecycle: parameter_object
+ endpoint: ntpparam
+
+ snmpmanager:
+ lifecycle: object_by_args
+ endpoint: snmpmanager
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes: []
+ delete_id_attributes:
+ - ipaddress
+ - netmask
+
+ snmptrap:
+ lifecycle: object_by_args
+ endpoint: snmptrap
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes: []
+ delete_id_attributes:
+ - trapdestination
+ - trapclass
+ - version
+
+ snmpcommunity:
+ lifecycle: object
+ endpoint: snmpcommunity
+ primary_id_attribute: communityname
+ resource_missing_errorcode: 258
+ allow_recreate: true
+ non_updateable_attributes:
+ - communityname
+ - permissions
+
+ systemuser:
+ lifecycle: object
+ endpoint: systemuser
+ primary_id_attribute: username
+ resource_missing_errorcode: 2626
+ allow_recreate: true
+ skip_attributes:
+ - password
+ non_updateable_attributes: []
diff --git a/citrixadc_framework/lbparameter/datasource_lbparameter.go b/citrixadc_framework/lbparameter/datasource_lbparameter.go
new file mode 100644
index 000000000..b84805ef7
--- /dev/null
+++ b/citrixadc_framework/lbparameter/datasource_lbparameter.go
@@ -0,0 +1,59 @@
+package lbparameter
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/citrix/adc-nitro-go/service"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+)
+
+var _ datasource.DataSource = (*LbParameterDataSource)(nil)
+
+func LBParameterDataSource() datasource.DataSource {
+ return &LbParameterDataSource{}
+}
+
+type LbParameterDataSource struct {
+ client *service.NitroClient
+}
+
+func (d *LbParameterDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_lbparameter"
+}
+
+func (d *LbParameterDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+ d.client = *req.ProviderData.(**service.NitroClient)
+}
+
+func (d *LbParameterDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = LBParameterDataSourceSchema()
+}
+
+func (d *LbParameterDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var data LbParameterResourceModel
+ // Read Terraform configuration data into the model
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var getResponseData map[string]interface{}
+ var err error
+
+ getResponseData, err = d.client.FindResource(service.Lbparameter.Type(), "")
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read lbparameter, got error: %s", err))
+ return
+ }
+
+ lbparameterSetAttrFromGet(ctx, &data, getResponseData)
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/citrixadc_framework/lbparameter/datasource_schema.go b/citrixadc_framework/lbparameter/datasource_schema.go
new file mode 100644
index 000000000..e8bd77f83
--- /dev/null
+++ b/citrixadc_framework/lbparameter/datasource_schema.go
@@ -0,0 +1,111 @@
+package lbparameter
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+)
+
+func LBParameterDataSourceSchema() schema.Schema {
+ return schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "allowboundsvcremoval": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "computedadccookieattribute": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "consolidatedlconn": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "cookiepassphrase": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "dbsttl": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "dropmqttjumbomessage": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "httponlycookieflag": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "literaladccookieattribute": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "maxpipelinenat": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "monitorconnectionclose": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "monitorskipmaxclient": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "preferdirectroute": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "proximityfromself": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "retainservicestate": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "startuprrfactor": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "storemqttclientidandusername": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "sessionsthreshold": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "undefaction": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "useencryptedpersistencecookie": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "useportforhashlb": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "usesecuredpersistencecookie": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "vserverspecificmac": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "lbhashalgorithm": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "lbhashfingers": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ },
+ }
+}
diff --git a/citrixadc_framework/lbparameter/resource_lbparameter.go b/citrixadc_framework/lbparameter/resource_lbparameter.go
new file mode 100644
index 000000000..0f85d8835
--- /dev/null
+++ b/citrixadc_framework/lbparameter/resource_lbparameter.go
@@ -0,0 +1,155 @@
+package lbparameter
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/citrix/adc-nitro-go/service"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ resource.Resource = &LbParameterResource{}
+var _ resource.ResourceWithConfigure = (*LbParameterResource)(nil)
+var _ resource.ResourceWithImportState = (*LbParameterResource)(nil)
+
+func NewLbParameterResource() resource.Resource {
+ return &LbParameterResource{}
+}
+
+// LbParameterResource defines the resource implementation.
+type LbParameterResource struct {
+ client *service.NitroClient
+}
+
+func (r *LbParameterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
+
+func (r *LbParameterResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_lbparameter"
+}
+
+func (r *LbParameterResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+ // Set the client for the resource.
+ r.client = *req.ProviderData.(**service.NitroClient)
+}
+
+func (r *LbParameterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data LbParameterResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Creating lbparameter resource")
+
+ lbparameter := lbparameterGetThePayloadFromtheConfig(ctx, &data)
+
+ // Make API call
+ err := r.client.UpdateUnnamedResource(service.Lbparameter.Type(), &lbparameter)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create lbparameter, got error: %s", err))
+ return
+ }
+
+ // Generate unique ID for this configuration resource
+ data.Id = types.StringValue("lbparameter-config")
+
+ tflog.Trace(ctx, "Created lbparameter resource")
+
+ // Read the updated state back
+ r.readLbParameterFromApi(ctx, &data, &resp.Diagnostics)
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *LbParameterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data LbParameterResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Reading lbparameter resource")
+
+ r.readLbParameterFromApi(ctx, &data, &resp.Diagnostics)
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *LbParameterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var data LbParameterResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Updating lbparameter resource")
+
+ // Create API request body from the model
+ lbparameter := lbparameterGetThePayloadFromtheConfig(ctx, &data)
+
+ // Make API call
+ err := r.client.UpdateUnnamedResource(service.Lbparameter.Type(), &lbparameter)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update lbparameter, got error: %s", err))
+ return
+ }
+
+ tflog.Trace(ctx, "Updated lbparameter resource")
+
+ // Read the updated state back
+ r.readLbParameterFromApi(ctx, &data, &resp.Diagnostics)
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *LbParameterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var data LbParameterResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Deleting lbparameter resource")
+
+ // For lbparameter, we don't actually delete the resource as it's a global configuration
+ // We just remove it from state
+ tflog.Trace(ctx, "Deleted lbparameter resource from state")
+}
+
+// Helper function to read lbparameter data from API
+func (r *LbParameterResource) readLbParameterFromApi(ctx context.Context, data *LbParameterResourceModel, diags *diag.Diagnostics) {
+ getResponseData, err := r.client.FindResource(service.Lbparameter.Type(), "")
+ if err != nil {
+ diags.AddError("Client Error", fmt.Sprintf("Unable to read lbparameter, got error: %s", err))
+ return
+ }
+
+ lbparameterSetAttrFromGet(ctx, data, getResponseData)
+
+}
diff --git a/citrixadc_framework/lbparameter/resource_schema.go b/citrixadc_framework/lbparameter/resource_schema.go
new file mode 100644
index 000000000..e239c2689
--- /dev/null
+++ b/citrixadc_framework/lbparameter/resource_schema.go
@@ -0,0 +1,370 @@
+package lbparameter
+
+import (
+ "context"
+
+ "github.com/citrix/adc-nitro-go/resource/config/lb"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+
+ "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework/utils"
+)
+
+// LbParameterResourceModel describes the resource data model.
+type LbParameterResourceModel struct {
+ Id types.String `tfsdk:"id"`
+ AllowBoundSvcRemoval types.String `tfsdk:"allowboundsvcremoval"`
+ ComputedAdcCookieAttribute types.String `tfsdk:"computedadccookieattribute"`
+ ConsolidatedLconn types.String `tfsdk:"consolidatedlconn"`
+ CookiePassphrase types.String `tfsdk:"cookiepassphrase"`
+ DbsTtl types.Int64 `tfsdk:"dbsttl"`
+ DropMqttJumboMessage types.String `tfsdk:"dropmqttjumbomessage"`
+ HttpOnlyCookieFlag types.String `tfsdk:"httponlycookieflag"`
+ LiteralAdcCookieAttribute types.String `tfsdk:"literaladccookieattribute"`
+ MaxPipelineNat types.Int64 `tfsdk:"maxpipelinenat"`
+ MonitorConnectionClose types.String `tfsdk:"monitorconnectionclose"`
+ MonitorSkipMaxClient types.String `tfsdk:"monitorskipmaxclient"`
+ PreferDirectRoute types.String `tfsdk:"preferdirectroute"`
+ ProximityFromSelf types.String `tfsdk:"proximityfromself"`
+ RetainServiceState types.String `tfsdk:"retainservicestate"`
+ StartupRrFactor types.Int64 `tfsdk:"startuprrfactor"`
+ StoreMqttClientIdAndUsername types.String `tfsdk:"storemqttclientidandusername"`
+ SessionsThreshold types.Int64 `tfsdk:"sessionsthreshold"`
+ UndefAction types.String `tfsdk:"undefaction"`
+ UseEncryptedPersistenceCookie types.String `tfsdk:"useencryptedpersistencecookie"`
+ UsePortForHashLb types.String `tfsdk:"useportforhashlb"`
+ UseSecuredPersistenceCookie types.String `tfsdk:"usesecuredpersistencecookie"`
+ VserverSpecificMac types.String `tfsdk:"vserverspecificmac"`
+ LbHashAlgorithm types.String `tfsdk:"lbhashalgorithm"`
+ LbHashFingers types.Int64 `tfsdk:"lbhashfingers"`
+}
+
+func (r *LbParameterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Version: 1,
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "allowboundsvcremoval": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "computedadccookieattribute": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "consolidatedlconn": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "cookiepassphrase": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "dbsttl": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "dropmqttjumbomessage": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "httponlycookieflag": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "literaladccookieattribute": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "maxpipelinenat": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "monitorconnectionclose": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "monitorskipmaxclient": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "preferdirectroute": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "proximityfromself": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "retainservicestate": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "startuprrfactor": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "storemqttclientidandusername": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "sessionsthreshold": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ "undefaction": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "useencryptedpersistencecookie": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "useportforhashlb": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "usesecuredpersistencecookie": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "vserverspecificmac": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "lbhashalgorithm": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ },
+ "lbhashfingers": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func lbparameterGetThePayloadFromtheConfig(ctx context.Context, data *LbParameterResourceModel) lb.Lbparameter {
+ tflog.Debug(ctx, "In lbparameterGetThePayloadFromtheConfig Function")
+
+ // Create API request body from the model
+ lbparameter := lb.Lbparameter{}
+
+ if !data.AllowBoundSvcRemoval.IsNull() {
+ lbparameter.Allowboundsvcremoval = data.AllowBoundSvcRemoval.ValueString()
+ }
+ if !data.ComputedAdcCookieAttribute.IsNull() {
+ lbparameter.Computedadccookieattribute = data.ComputedAdcCookieAttribute.ValueString()
+ }
+ if !data.ConsolidatedLconn.IsNull() {
+ lbparameter.Consolidatedlconn = data.ConsolidatedLconn.ValueString()
+ }
+ if !data.CookiePassphrase.IsNull() {
+ lbparameter.Cookiepassphrase = data.CookiePassphrase.ValueString()
+ }
+ if !data.DbsTtl.IsNull() {
+ lbparameter.Dbsttl = utils.IntPtr(int(data.DbsTtl.ValueInt64()))
+ }
+ if !data.DropMqttJumboMessage.IsNull() {
+ lbparameter.Dropmqttjumbomessage = data.DropMqttJumboMessage.ValueString()
+ }
+ if !data.HttpOnlyCookieFlag.IsNull() {
+ lbparameter.Httponlycookieflag = data.HttpOnlyCookieFlag.ValueString()
+ }
+ if !data.LiteralAdcCookieAttribute.IsNull() {
+ lbparameter.Literaladccookieattribute = data.LiteralAdcCookieAttribute.ValueString()
+ }
+ if !data.MaxPipelineNat.IsNull() {
+ lbparameter.Maxpipelinenat = utils.IntPtr(int(data.MaxPipelineNat.ValueInt64()))
+ }
+ if !data.MonitorConnectionClose.IsNull() {
+ lbparameter.Monitorconnectionclose = data.MonitorConnectionClose.ValueString()
+ }
+ if !data.MonitorSkipMaxClient.IsNull() {
+ lbparameter.Monitorskipmaxclient = data.MonitorSkipMaxClient.ValueString()
+ }
+ if !data.PreferDirectRoute.IsNull() {
+ lbparameter.Preferdirectroute = data.PreferDirectRoute.ValueString()
+ }
+ if !data.ProximityFromSelf.IsNull() {
+ lbparameter.Proximityfromself = data.ProximityFromSelf.ValueString()
+ }
+ if !data.RetainServiceState.IsNull() {
+ lbparameter.Retainservicestate = data.RetainServiceState.ValueString()
+ }
+ if !data.StartupRrFactor.IsNull() {
+ lbparameter.Startuprrfactor = utils.IntPtr(int(data.StartupRrFactor.ValueInt64()))
+ }
+ if !data.StoreMqttClientIdAndUsername.IsNull() {
+ lbparameter.Storemqttclientidandusername = data.StoreMqttClientIdAndUsername.ValueString()
+ }
+ if !data.UndefAction.IsNull() {
+ lbparameter.Undefaction = data.UndefAction.ValueString()
+ }
+ if !data.UseEncryptedPersistenceCookie.IsNull() {
+ lbparameter.Useencryptedpersistencecookie = data.UseEncryptedPersistenceCookie.ValueString()
+ }
+ if !data.UsePortForHashLb.IsNull() {
+ lbparameter.Useportforhashlb = data.UsePortForHashLb.ValueString()
+ }
+ if !data.UseSecuredPersistenceCookie.IsNull() {
+ lbparameter.Usesecuredpersistencecookie = data.UseSecuredPersistenceCookie.ValueString()
+ }
+ if !data.VserverSpecificMac.IsNull() {
+ lbparameter.Vserverspecificmac = data.VserverSpecificMac.ValueString()
+ }
+ if !data.LbHashAlgorithm.IsNull() {
+ lbparameter.Lbhashalgorithm = data.LbHashAlgorithm.ValueString()
+ }
+ if !data.LbHashFingers.IsNull() {
+ lbparameter.Lbhashfingers = utils.IntPtr(int(data.LbHashFingers.ValueInt64()))
+ }
+
+ return lbparameter
+}
+
+func lbparameterSetAttrFromGet(ctx context.Context, data *LbParameterResourceModel, getResponseData map[string]interface{}) *LbParameterResourceModel {
+ tflog.Debug(ctx, "In lbparameterSetAttrFromGet Function")
+
+ // Set ID for the resource
+ data.Id = types.StringValue("lbparameter-config")
+
+ // Convert API response to model
+ if val, ok := getResponseData["allowboundsvcremoval"]; ok && val != nil {
+ data.AllowBoundSvcRemoval = types.StringValue(val.(string))
+ } else {
+ data.AllowBoundSvcRemoval = types.StringNull()
+ }
+ if val, ok := getResponseData["computedadccookieattribute"]; ok && val != nil {
+ data.ComputedAdcCookieAttribute = types.StringValue(val.(string))
+ } else {
+ data.ComputedAdcCookieAttribute = types.StringNull()
+ }
+ if val, ok := getResponseData["consolidatedlconn"]; ok && val != nil {
+ data.ConsolidatedLconn = types.StringValue(val.(string))
+ } else {
+ data.ConsolidatedLconn = types.StringNull()
+ }
+ if val, ok := getResponseData["cookiepassphrase"]; ok && val != nil {
+ data.CookiePassphrase = types.StringValue(val.(string))
+ } else {
+ data.CookiePassphrase = types.StringNull()
+ }
+ if val, ok := getResponseData["dbsttl"]; ok && val != nil {
+ if intVal, err := utils.ConvertToInt64(val); err == nil {
+ data.DbsTtl = types.Int64Value(intVal)
+ }
+ } else {
+ data.DbsTtl = types.Int64Null()
+ }
+ if val, ok := getResponseData["dropmqttjumbomessage"]; ok && val != nil {
+ data.DropMqttJumboMessage = types.StringValue(val.(string))
+ } else {
+ data.DropMqttJumboMessage = types.StringNull()
+ }
+ if val, ok := getResponseData["httponlycookieflag"]; ok && val != nil {
+ data.HttpOnlyCookieFlag = types.StringValue(val.(string))
+ } else {
+ data.HttpOnlyCookieFlag = types.StringNull()
+ }
+ if val, ok := getResponseData["literaladccookieattribute"]; ok && val != nil {
+ data.LiteralAdcCookieAttribute = types.StringValue(val.(string))
+ } else {
+ data.LiteralAdcCookieAttribute = types.StringNull()
+ }
+ if val, ok := getResponseData["maxpipelinenat"]; ok && val != nil {
+ if intVal, err := utils.ConvertToInt64(val); err == nil {
+ data.MaxPipelineNat = types.Int64Value(intVal)
+ }
+ } else {
+ data.MaxPipelineNat = types.Int64Null()
+ }
+ if val, ok := getResponseData["monitorconnectionclose"]; ok && val != nil {
+ data.MonitorConnectionClose = types.StringValue(val.(string))
+ } else {
+ data.MonitorConnectionClose = types.StringNull()
+ }
+ if val, ok := getResponseData["monitorskipmaxclient"]; ok && val != nil {
+ data.MonitorSkipMaxClient = types.StringValue(val.(string))
+ } else {
+ data.MonitorSkipMaxClient = types.StringNull()
+ }
+ if val, ok := getResponseData["preferdirectroute"]; ok && val != nil {
+ data.PreferDirectRoute = types.StringValue(val.(string))
+ } else {
+ data.PreferDirectRoute = types.StringNull()
+ }
+ if val, ok := getResponseData["proximityfromself"]; ok && val != nil {
+ data.ProximityFromSelf = types.StringValue(val.(string))
+ } else {
+ data.ProximityFromSelf = types.StringNull()
+ }
+ if val, ok := getResponseData["retainservicestate"]; ok && val != nil {
+ data.RetainServiceState = types.StringValue(val.(string))
+ } else {
+ data.RetainServiceState = types.StringNull()
+ }
+ if val, ok := getResponseData["startuprrfactor"]; ok && val != nil {
+ if intVal, err := utils.ConvertToInt64(val); err == nil {
+ data.StartupRrFactor = types.Int64Value(intVal)
+ }
+ } else {
+ data.StartupRrFactor = types.Int64Null()
+ }
+ if val, ok := getResponseData["storemqttclientidandusername"]; ok && val != nil {
+ data.StoreMqttClientIdAndUsername = types.StringValue(val.(string))
+ } else {
+ data.StoreMqttClientIdAndUsername = types.StringNull()
+ }
+ if val, ok := getResponseData["sessionsthreshold"]; ok && val != nil {
+ if intVal, err := utils.ConvertToInt64(val); err == nil {
+ data.SessionsThreshold = types.Int64Value(intVal)
+ }
+ } else {
+ data.SessionsThreshold = types.Int64Null()
+ }
+ if val, ok := getResponseData["undefaction"]; ok && val != nil {
+ data.UndefAction = types.StringValue(val.(string))
+ } else {
+ data.UndefAction = types.StringNull()
+ }
+ if val, ok := getResponseData["useencryptedpersistencecookie"]; ok && val != nil {
+ data.UseEncryptedPersistenceCookie = types.StringValue(val.(string))
+ } else {
+ data.UseEncryptedPersistenceCookie = types.StringNull()
+ }
+ if val, ok := getResponseData["useportforhashlb"]; ok && val != nil {
+ data.UsePortForHashLb = types.StringValue(val.(string))
+ } else {
+ data.UsePortForHashLb = types.StringNull()
+ }
+ if val, ok := getResponseData["usesecuredpersistencecookie"]; ok && val != nil {
+ data.UseSecuredPersistenceCookie = types.StringValue(val.(string))
+ } else {
+ data.UseSecuredPersistenceCookie = types.StringNull()
+ }
+ if val, ok := getResponseData["vserverspecificmac"]; ok && val != nil {
+ data.VserverSpecificMac = types.StringValue(val.(string))
+ } else {
+ data.VserverSpecificMac = types.StringNull()
+ }
+ if val, ok := getResponseData["lbhashalgorithm"]; ok && val != nil {
+ data.LbHashAlgorithm = types.StringValue(val.(string))
+ } else {
+ data.LbHashAlgorithm = types.StringNull()
+ }
+ if val, ok := getResponseData["lbhashfingers"]; ok && val != nil {
+ if intVal, err := utils.ConvertToInt64(val); err == nil {
+ data.LbHashFingers = types.Int64Value(intVal)
+ }
+ } else {
+ data.LbHashFingers = types.Int64Null()
+ }
+
+ return data
+}
diff --git a/citrixadc_framework/provider.go b/citrixadc_framework/provider/provider.go
similarity index 92%
rename from citrixadc_framework/provider.go
rename to citrixadc_framework/provider/provider.go
index 4b626bfda..62d5c3425 100644
--- a/citrixadc_framework/provider.go
+++ b/citrixadc_framework/provider/provider.go
@@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package citrixadc_framework
+package provider
import (
"context"
"os"
+ "github.com/citrix/adc-nitro-go/service"
+
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
@@ -27,7 +29,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
- "github.com/citrix/adc-nitro-go/service"
+ "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework/lbparameter"
+ "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework/sslcertkey"
)
// Ensure CitrixAdcFrameworkProvider satisfies various provider interfaces.
@@ -212,27 +215,31 @@ func (p *CitrixAdcFrameworkProvider) Configure(ctx context.Context, req provider
}
}
- providerData := &ProviderData{
- Client: client,
- Username: username,
- Password: password,
- Endpoint: endpoint,
- }
+ // providerData := &ProviderData{
+ // Client: client,
+ // Username: username,
+ // Password: password,
+ // Endpoint: endpoint,
+ // }
- resp.DataSourceData = providerData
- resp.ResourceData = providerData
+ resp.DataSourceData = &client
+ resp.ResourceData = &client
tflog.Info(ctx, "Configured CitrixADC Framework Provider", map[string]any{"success": true})
}
func (p *CitrixAdcFrameworkProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
- NewLbParameterResource,
+ lbparameter.NewLbParameterResource,
+ sslcertkey.NewSslCertKeyResource,
}
}
func (p *CitrixAdcFrameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
- return []func() datasource.DataSource{}
+ return []func() datasource.DataSource{
+ lbparameter.LBParameterDataSource,
+ sslcertkey.SslCertKeyDataSource,
+ }
}
func New(version string) func() provider.Provider {
diff --git a/citrixadc_framework/resource_citrixadc_lbparameter.go b/citrixadc_framework/resource_citrixadc_lbparameter.go
deleted file mode 100644
index bfb46bc24..000000000
--- a/citrixadc_framework/resource_citrixadc_lbparameter.go
+++ /dev/null
@@ -1,620 +0,0 @@
-package citrixadc_framework
-
-import (
- "context"
- "fmt"
- "strconv"
-
- "github.com/citrix/adc-nitro-go/service"
- "github.com/hashicorp/terraform-plugin-framework/diag"
- "github.com/hashicorp/terraform-plugin-framework/resource"
- "github.com/hashicorp/terraform-plugin-framework/resource/schema"
- "github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-log/tflog"
-)
-
-// Ensure provider defined types fully satisfy framework interfaces.
-var _ resource.Resource = &LbParameterResource{}
-
-func NewLbParameterResource() resource.Resource {
- return &LbParameterResource{}
-}
-
-// LbParameterResource defines the resource implementation.
-type LbParameterResource struct {
- providerData *ProviderData
-}
-
-// LbParameterResourceModel describes the resource data model.
-type LbParameterResourceModel struct {
- Id types.String `tfsdk:"id"`
- AllowBoundSvcRemoval types.String `tfsdk:"allowboundsvcremoval"`
- ComputedAdcCookieAttribute types.String `tfsdk:"computedadccookieattribute"`
- ConsolidatedLconn types.String `tfsdk:"consolidatedlconn"`
- CookiePassphrase types.String `tfsdk:"cookiepassphrase"`
- DbsTtl types.Int64 `tfsdk:"dbsttl"`
- DropMqttJumboMessage types.String `tfsdk:"dropmqttjumbomessage"`
- HttpOnlyCookieFlag types.String `tfsdk:"httponlycookieflag"`
- LiteralAdcCookieAttribute types.String `tfsdk:"literaladccookieattribute"`
- MaxPipelineNat types.Int64 `tfsdk:"maxpipelinenat"`
- MonitorConnectionClose types.String `tfsdk:"monitorconnectionclose"`
- MonitorSkipMaxClient types.String `tfsdk:"monitorskipmaxclient"`
- PreferDirectRoute types.String `tfsdk:"preferdirectroute"`
- ProximityFromSelf types.String `tfsdk:"proximityfromself"`
- RetainServiceState types.String `tfsdk:"retainservicestate"`
- StartupRrFactor types.Int64 `tfsdk:"startuprrfactor"`
- StoreMqttClientIdAndUsername types.String `tfsdk:"storemqttclientidandusername"`
- SessionsThreshold types.Int64 `tfsdk:"sessionsthreshold"`
- UndefAction types.String `tfsdk:"undefaction"`
- UseEncryptedPersistenceCookie types.String `tfsdk:"useencryptedpersistencecookie"`
- UsePortForHashLb types.String `tfsdk:"useportforhashlb"`
- UseSecuredPersistenceCookie types.String `tfsdk:"usesecuredpersistencecookie"`
- VserverSpecificMac types.String `tfsdk:"vserverspecificmac"`
- LbHashAlgorithm types.String `tfsdk:"lbhashalgorithm"`
- LbHashFingers types.Int64 `tfsdk:"lbhashfingers"`
-}
-
-type Lbparameter struct {
- Adccookieattributewarningmsg string `json:"adccookieattributewarningmsg,omitempty"`
- Allowboundsvcremoval string `json:"allowboundsvcremoval,omitempty"`
- Builtin interface{} `json:"builtin,omitempty"`
- Computedadccookieattribute string `json:"computedadccookieattribute,omitempty"`
- Consolidatedlconn string `json:"consolidatedlconn,omitempty"`
- Cookiepassphrase string `json:"cookiepassphrase,omitempty"`
- Dbsttl *int `json:"dbsttl,omitempty"`
- Dropmqttjumbomessage string `json:"dropmqttjumbomessage,omitempty"`
- Feature string `json:"feature,omitempty"`
- Httponlycookieflag string `json:"httponlycookieflag,omitempty"`
- Literaladccookieattribute string `json:"literaladccookieattribute,omitempty"`
- Maxpipelinenat *int `json:"maxpipelinenat,omitempty"`
- Monitorconnectionclose string `json:"monitorconnectionclose,omitempty"`
- Monitorskipmaxclient string `json:"monitorskipmaxclient,omitempty"`
- Preferdirectroute string `json:"preferdirectroute,omitempty"`
- Proximityfromself string `json:"proximityfromself,omitempty"`
- Retainservicestate string `json:"retainservicestate,omitempty"`
- Sessionsthreshold *int `json:"sessionsthreshold,omitempty"`
- Startuprrfactor *int `json:"startuprrfactor,omitempty"`
- Storemqttclientidandusername string `json:"storemqttclientidandusername,omitempty"`
- Undefaction string `json:"undefaction,omitempty"`
- Useencryptedpersistencecookie string `json:"useencryptedpersistencecookie,omitempty"`
- Useportforhashlb string `json:"useportforhashlb,omitempty"`
- Usesecuredpersistencecookie string `json:"usesecuredpersistencecookie,omitempty"`
- Vserverspecificmac string `json:"vserverspecificmac,omitempty"`
- Lbhashalgorithm string `json:"lbhashalgorithm,omitempty"`
- Lbhashfingers *int `json:"lbhashfingers,omitempty"`
-}
-
-func (r *LbParameterResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
- resp.TypeName = req.ProviderTypeName + "_lbparameter"
-}
-
-func (r *LbParameterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Version: 1,
- Attributes: map[string]schema.Attribute{
- "id": schema.StringAttribute{
- Computed: true,
- },
- "allowboundsvcremoval": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "computedadccookieattribute": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "consolidatedlconn": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "cookiepassphrase": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "dbsttl": schema.Int64Attribute{
- Optional: true,
- Computed: true,
- },
- "dropmqttjumbomessage": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "httponlycookieflag": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "literaladccookieattribute": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "maxpipelinenat": schema.Int64Attribute{
- Optional: true,
- Computed: true,
- },
- "monitorconnectionclose": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "monitorskipmaxclient": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "preferdirectroute": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "proximityfromself": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "retainservicestate": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "startuprrfactor": schema.Int64Attribute{
- Optional: true,
- Computed: true,
- },
- "storemqttclientidandusername": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "sessionsthreshold": schema.Int64Attribute{
- Optional: true,
- Computed: true,
- },
- "undefaction": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "useencryptedpersistencecookie": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "useportforhashlb": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "usesecuredpersistencecookie": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "vserverspecificmac": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "lbhashalgorithm": schema.StringAttribute{
- Optional: true,
- Computed: true,
- },
- "lbhashfingers": schema.Int64Attribute{
- Optional: true,
- Computed: true,
- },
- },
- }
-}
-
-func (r *LbParameterResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
- // Prevent panic if the provider has not been configured.
- if req.ProviderData == nil {
- return
- }
-
- providerData, ok := req.ProviderData.(*ProviderData)
-
- if !ok {
- resp.Diagnostics.AddError(
- "Unexpected Resource Configure Type",
- fmt.Sprintf("Expected *ProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
- )
-
- return
- }
-
- r.providerData = providerData
-}
-
-func (r *LbParameterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data LbParameterResourceModel
-
- // Read Terraform plan data into the model
- resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
-
- if resp.Diagnostics.HasError() {
- return
- }
-
- tflog.Debug(ctx, "Creating lbparameter resource")
-
- // Create API request body from the model
- lbparameter := Lbparameter{}
-
- if !data.AllowBoundSvcRemoval.IsNull() {
- lbparameter.Allowboundsvcremoval = data.AllowBoundSvcRemoval.ValueString()
- }
- if !data.ComputedAdcCookieAttribute.IsNull() {
- lbparameter.Computedadccookieattribute = data.ComputedAdcCookieAttribute.ValueString()
- }
- if !data.ConsolidatedLconn.IsNull() {
- lbparameter.Consolidatedlconn = data.ConsolidatedLconn.ValueString()
- }
- if !data.CookiePassphrase.IsNull() {
- lbparameter.Cookiepassphrase = data.CookiePassphrase.ValueString()
- }
- if !data.DbsTtl.IsNull() {
- lbparameter.Dbsttl = intPtr(int(data.DbsTtl.ValueInt64()))
- }
- if !data.DropMqttJumboMessage.IsNull() {
- lbparameter.Dropmqttjumbomessage = data.DropMqttJumboMessage.ValueString()
- }
- if !data.HttpOnlyCookieFlag.IsNull() {
- lbparameter.Httponlycookieflag = data.HttpOnlyCookieFlag.ValueString()
- }
- if !data.LiteralAdcCookieAttribute.IsNull() {
- lbparameter.Literaladccookieattribute = data.LiteralAdcCookieAttribute.ValueString()
- }
- if !data.MaxPipelineNat.IsNull() {
- lbparameter.Maxpipelinenat = intPtr(int(data.MaxPipelineNat.ValueInt64()))
- }
- if !data.MonitorConnectionClose.IsNull() {
- lbparameter.Monitorconnectionclose = data.MonitorConnectionClose.ValueString()
- }
- if !data.MonitorSkipMaxClient.IsNull() {
- lbparameter.Monitorskipmaxclient = data.MonitorSkipMaxClient.ValueString()
- }
- if !data.PreferDirectRoute.IsNull() {
- lbparameter.Preferdirectroute = data.PreferDirectRoute.ValueString()
- }
- if !data.ProximityFromSelf.IsNull() {
- lbparameter.Proximityfromself = data.ProximityFromSelf.ValueString()
- }
- if !data.RetainServiceState.IsNull() {
- lbparameter.Retainservicestate = data.RetainServiceState.ValueString()
- }
- if !data.StartupRrFactor.IsNull() {
- lbparameter.Startuprrfactor = intPtr(int(data.StartupRrFactor.ValueInt64()))
- }
- if !data.StoreMqttClientIdAndUsername.IsNull() {
- lbparameter.Storemqttclientidandusername = data.StoreMqttClientIdAndUsername.ValueString()
- }
- if !data.SessionsThreshold.IsNull() {
- lbparameter.Sessionsthreshold = intPtr(int(data.SessionsThreshold.ValueInt64()))
- }
- if !data.UndefAction.IsNull() {
- lbparameter.Undefaction = data.UndefAction.ValueString()
- }
- if !data.UseEncryptedPersistenceCookie.IsNull() {
- lbparameter.Useencryptedpersistencecookie = data.UseEncryptedPersistenceCookie.ValueString()
- }
- if !data.UsePortForHashLb.IsNull() {
- lbparameter.Useportforhashlb = data.UsePortForHashLb.ValueString()
- }
- if !data.UseSecuredPersistenceCookie.IsNull() {
- lbparameter.Usesecuredpersistencecookie = data.UseSecuredPersistenceCookie.ValueString()
- }
- if !data.VserverSpecificMac.IsNull() {
- lbparameter.Vserverspecificmac = data.VserverSpecificMac.ValueString()
- }
- if !data.LbHashAlgorithm.IsNull() {
- lbparameter.Lbhashalgorithm = data.LbHashAlgorithm.ValueString()
- }
- if !data.LbHashFingers.IsNull() {
- lbparameter.Lbhashfingers = intPtr(int(data.LbHashFingers.ValueInt64()))
- }
-
- // Make API call
- err := r.providerData.Client.UpdateUnnamedResource(service.Lbparameter.Type(), &lbparameter)
- if err != nil {
- resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create lbparameter, got error: %s", err))
- return
- }
-
- // Generate unique ID for this configuration resource
- data.Id = types.StringValue("lbparameter-config")
-
- tflog.Trace(ctx, "Created lbparameter resource")
-
- // Read the updated state back
- r.readLbParameterFromApi(ctx, &data, &resp.Diagnostics)
-
- // Save data into Terraform state
- resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
-}
-
-func (r *LbParameterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
- var data LbParameterResourceModel
-
- // Read Terraform prior state data into the model
- resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
-
- if resp.Diagnostics.HasError() {
- return
- }
-
- tflog.Debug(ctx, "Reading lbparameter resource")
-
- r.readLbParameterFromApi(ctx, &data, &resp.Diagnostics)
-
- // Save updated data into Terraform state
- resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
-}
-
-func (r *LbParameterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
- var data LbParameterResourceModel
-
- // Read Terraform plan data into the model
- resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
-
- if resp.Diagnostics.HasError() {
- return
- }
-
- tflog.Debug(ctx, "Updating lbparameter resource")
-
- // Create API request body from the model
- lbparameter := Lbparameter{}
-
- if !data.AllowBoundSvcRemoval.IsNull() {
- lbparameter.Allowboundsvcremoval = data.AllowBoundSvcRemoval.ValueString()
- }
- if !data.ComputedAdcCookieAttribute.IsNull() {
- lbparameter.Computedadccookieattribute = data.ComputedAdcCookieAttribute.ValueString()
- }
- if !data.ConsolidatedLconn.IsNull() {
- lbparameter.Consolidatedlconn = data.ConsolidatedLconn.ValueString()
- }
- if !data.CookiePassphrase.IsNull() {
- lbparameter.Cookiepassphrase = data.CookiePassphrase.ValueString()
- }
- if !data.DbsTtl.IsNull() {
- lbparameter.Dbsttl = intPtr(int(data.DbsTtl.ValueInt64()))
- }
- if !data.DropMqttJumboMessage.IsNull() {
- lbparameter.Dropmqttjumbomessage = data.DropMqttJumboMessage.ValueString()
- }
- if !data.HttpOnlyCookieFlag.IsNull() {
- lbparameter.Httponlycookieflag = data.HttpOnlyCookieFlag.ValueString()
- }
- if !data.LiteralAdcCookieAttribute.IsNull() {
- lbparameter.Literaladccookieattribute = data.LiteralAdcCookieAttribute.ValueString()
- }
- if !data.MaxPipelineNat.IsNull() {
- lbparameter.Maxpipelinenat = intPtr(int(data.MaxPipelineNat.ValueInt64()))
- }
- if !data.MonitorConnectionClose.IsNull() {
- lbparameter.Monitorconnectionclose = data.MonitorConnectionClose.ValueString()
- }
- if !data.MonitorSkipMaxClient.IsNull() {
- lbparameter.Monitorskipmaxclient = data.MonitorSkipMaxClient.ValueString()
- }
- if !data.PreferDirectRoute.IsNull() {
- lbparameter.Preferdirectroute = data.PreferDirectRoute.ValueString()
- }
- if !data.ProximityFromSelf.IsNull() {
- lbparameter.Proximityfromself = data.ProximityFromSelf.ValueString()
- }
- if !data.RetainServiceState.IsNull() {
- lbparameter.Retainservicestate = data.RetainServiceState.ValueString()
- }
- if !data.StartupRrFactor.IsNull() {
- lbparameter.Startuprrfactor = intPtr(int(data.StartupRrFactor.ValueInt64()))
- }
- if !data.StoreMqttClientIdAndUsername.IsNull() {
- lbparameter.Storemqttclientidandusername = data.StoreMqttClientIdAndUsername.ValueString()
- }
- if !data.SessionsThreshold.IsNull() {
- lbparameter.Sessionsthreshold = intPtr(int(data.SessionsThreshold.ValueInt64()))
- }
- if !data.UndefAction.IsNull() {
- lbparameter.Undefaction = data.UndefAction.ValueString()
- }
- if !data.UseEncryptedPersistenceCookie.IsNull() {
- lbparameter.Useencryptedpersistencecookie = data.UseEncryptedPersistenceCookie.ValueString()
- }
- if !data.UsePortForHashLb.IsNull() {
- lbparameter.Useportforhashlb = data.UsePortForHashLb.ValueString()
- }
- if !data.UseSecuredPersistenceCookie.IsNull() {
- lbparameter.Usesecuredpersistencecookie = data.UseSecuredPersistenceCookie.ValueString()
- }
- if !data.VserverSpecificMac.IsNull() {
- lbparameter.Vserverspecificmac = data.VserverSpecificMac.ValueString()
- }
- if !data.LbHashAlgorithm.IsNull() {
- lbparameter.Lbhashalgorithm = data.LbHashAlgorithm.ValueString()
- }
- if !data.LbHashFingers.IsNull() {
- lbparameter.Lbhashfingers = intPtr(int(data.LbHashFingers.ValueInt64()))
- }
-
- // Make API call
- err := r.providerData.Client.UpdateUnnamedResource(service.Lbparameter.Type(), &lbparameter)
- if err != nil {
- resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update lbparameter, got error: %s", err))
- return
- }
-
- tflog.Trace(ctx, "Updated lbparameter resource")
-
- // Read the updated state back
- r.readLbParameterFromApi(ctx, &data, &resp.Diagnostics)
-
- // Save updated data into Terraform state
- resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
-}
-
-func (r *LbParameterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
- var data LbParameterResourceModel
-
- // Read Terraform prior state data into the model
- resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
-
- if resp.Diagnostics.HasError() {
- return
- }
-
- tflog.Debug(ctx, "Deleting lbparameter resource")
-
- // For lbparameter, we don't actually delete the resource as it's a global configuration
- // We just remove it from state
- tflog.Trace(ctx, "Deleted lbparameter resource from state")
-}
-
-// Helper function to read lbparameter data from API
-func (r *LbParameterResource) readLbParameterFromApi(ctx context.Context, data *LbParameterResourceModel, diags *diag.Diagnostics) {
- result, err := r.providerData.Client.FindResource(service.Lbparameter.Type(), "")
- if err != nil {
- diags.AddError("Client Error", fmt.Sprintf("Unable to read lbparameter, got error: %s", err))
- return
- }
-
- // Set ID for the resource
- data.Id = types.StringValue("lbparameter-config")
-
- // Convert API response to model
- if val, ok := result["allowboundsvcremoval"]; ok && val != nil {
- data.AllowBoundSvcRemoval = types.StringValue(val.(string))
- } else {
- data.AllowBoundSvcRemoval = types.StringNull()
- }
- if val, ok := result["computedadccookieattribute"]; ok && val != nil {
- data.ComputedAdcCookieAttribute = types.StringValue(val.(string))
- } else {
- data.ComputedAdcCookieAttribute = types.StringNull()
- }
- if val, ok := result["consolidatedlconn"]; ok && val != nil {
- data.ConsolidatedLconn = types.StringValue(val.(string))
- } else {
- data.ConsolidatedLconn = types.StringNull()
- }
- if val, ok := result["cookiepassphrase"]; ok && val != nil {
- data.CookiePassphrase = types.StringValue(val.(string))
- } else {
- data.CookiePassphrase = types.StringNull()
- }
- if val, ok := result["dbsttl"]; ok && val != nil {
- if intVal, err := convertToInt64(val); err == nil {
- data.DbsTtl = types.Int64Value(intVal)
- }
- } else {
- data.DbsTtl = types.Int64Null()
- }
- if val, ok := result["dropmqttjumbomessage"]; ok && val != nil {
- data.DropMqttJumboMessage = types.StringValue(val.(string))
- } else {
- data.DropMqttJumboMessage = types.StringNull()
- }
- if val, ok := result["httponlycookieflag"]; ok && val != nil {
- data.HttpOnlyCookieFlag = types.StringValue(val.(string))
- } else {
- data.HttpOnlyCookieFlag = types.StringNull()
- }
- if val, ok := result["literaladccookieattribute"]; ok && val != nil {
- data.LiteralAdcCookieAttribute = types.StringValue(val.(string))
- } else {
- data.LiteralAdcCookieAttribute = types.StringNull()
- }
- if val, ok := result["maxpipelinenat"]; ok && val != nil {
- if intVal, err := convertToInt64(val); err == nil {
- data.MaxPipelineNat = types.Int64Value(intVal)
- }
- } else {
- data.MaxPipelineNat = types.Int64Null()
- }
- if val, ok := result["monitorconnectionclose"]; ok && val != nil {
- data.MonitorConnectionClose = types.StringValue(val.(string))
- } else {
- data.MonitorConnectionClose = types.StringNull()
- }
- if val, ok := result["monitorskipmaxclient"]; ok && val != nil {
- data.MonitorSkipMaxClient = types.StringValue(val.(string))
- } else {
- data.MonitorSkipMaxClient = types.StringNull()
- }
- if val, ok := result["preferdirectroute"]; ok && val != nil {
- data.PreferDirectRoute = types.StringValue(val.(string))
- } else {
- data.PreferDirectRoute = types.StringNull()
- }
- if val, ok := result["proximityfromself"]; ok && val != nil {
- data.ProximityFromSelf = types.StringValue(val.(string))
- } else {
- data.ProximityFromSelf = types.StringNull()
- }
- if val, ok := result["retainservicestate"]; ok && val != nil {
- data.RetainServiceState = types.StringValue(val.(string))
- } else {
- data.RetainServiceState = types.StringNull()
- }
- if val, ok := result["startuprrfactor"]; ok && val != nil {
- if intVal, err := convertToInt64(val); err == nil {
- data.StartupRrFactor = types.Int64Value(intVal)
- }
- } else {
- data.StartupRrFactor = types.Int64Null()
- }
- if val, ok := result["storemqttclientidandusername"]; ok && val != nil {
- data.StoreMqttClientIdAndUsername = types.StringValue(val.(string))
- } else {
- data.StoreMqttClientIdAndUsername = types.StringNull()
- }
- if val, ok := result["sessionsthreshold"]; ok && val != nil {
- if intVal, err := convertToInt64(val); err == nil {
- data.SessionsThreshold = types.Int64Value(intVal)
- }
- } else {
- data.SessionsThreshold = types.Int64Null()
- }
- if val, ok := result["undefaction"]; ok && val != nil {
- data.UndefAction = types.StringValue(val.(string))
- } else {
- data.UndefAction = types.StringNull()
- }
- if val, ok := result["useencryptedpersistencecookie"]; ok && val != nil {
- data.UseEncryptedPersistenceCookie = types.StringValue(val.(string))
- } else {
- data.UseEncryptedPersistenceCookie = types.StringNull()
- }
- if val, ok := result["useportforhashlb"]; ok && val != nil {
- data.UsePortForHashLb = types.StringValue(val.(string))
- } else {
- data.UsePortForHashLb = types.StringNull()
- }
- if val, ok := result["usesecuredpersistencecookie"]; ok && val != nil {
- data.UseSecuredPersistenceCookie = types.StringValue(val.(string))
- } else {
- data.UseSecuredPersistenceCookie = types.StringNull()
- }
- if val, ok := result["vserverspecificmac"]; ok && val != nil {
- data.VserverSpecificMac = types.StringValue(val.(string))
- } else {
- data.VserverSpecificMac = types.StringNull()
- }
- if val, ok := result["lbhashalgorithm"]; ok && val != nil {
- data.LbHashAlgorithm = types.StringValue(val.(string))
- } else {
- data.LbHashAlgorithm = types.StringNull()
- }
- if val, ok := result["lbhashfingers"]; ok && val != nil {
- if intVal, err := convertToInt64(val); err == nil {
- data.LbHashFingers = types.Int64Value(intVal)
- }
- } else {
- data.LbHashFingers = types.Int64Null()
- }
-}
-
-// Helper function to convert interface{} to int64
-func convertToInt64(value interface{}) (int64, error) {
- switch v := value.(type) {
- case int:
- return int64(v), nil
- case int64:
- return v, nil
- case string:
- return strconv.ParseInt(v, 10, 64)
- default:
- return 0, fmt.Errorf("cannot convert %T to int64", value)
- }
-}
diff --git a/citrixadc_framework/sslcertkey/datasource_schema.go b/citrixadc_framework/sslcertkey/datasource_schema.go
new file mode 100644
index 000000000..c747515bf
--- /dev/null
+++ b/citrixadc_framework/sslcertkey/datasource_schema.go
@@ -0,0 +1,89 @@
+package sslcertkey
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+)
+
+func SslCertKeyDataSourceSchema() schema.Schema {
+ return schema.Schema{
+ Description: "Data source to read SSL certificate key pair configuration.",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: "The ID of the SSL certificate key pair.",
+ },
+ "certkey": schema.StringAttribute{
+ Required: true,
+ Description: "Name of the certificate and private-key pair to read.",
+ },
+ "cert": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of and path to the X509 certificate file.",
+ },
+ "key": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of and path to the private-key file.",
+ },
+ "password": schema.BoolAttribute{
+ Computed: true,
+ Description: "Passphrase that was used to encrypt the private-key.",
+ },
+ "fipskey": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of the FIPS key in the Hardware Security Module (HSM).",
+ },
+ "hsmkey": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of the HSM key in the External Hardware Security Module (HSM).",
+ },
+ "inform": schema.StringAttribute{
+ Computed: true,
+ Description: "Input format of the certificate and the private-key files (PEM, DER, or PFX).",
+ },
+ "expirymonitor": schema.StringAttribute{
+ Computed: true,
+ Description: "Issue an alert when the certificate is about to expire.",
+ },
+ "notificationperiod": schema.Int64Attribute{
+ Computed: true,
+ Description: "Time, in days, before certificate expiration at which to generate an alert.",
+ },
+ "bundle": schema.StringAttribute{
+ Computed: true,
+ Description: "Parse the certificate chain as a single file.",
+ },
+ "linkcertkeyname": schema.StringAttribute{
+ Computed: true,
+ Description: "Name of the Certificate Authority certificate-key pair linked to this certificate.",
+ },
+ "nodomaincheck": schema.BoolAttribute{
+ Computed: true,
+ Description: "Override the check for matching domain names during certificate update.",
+ },
+ "ocspstaplingcache": schema.BoolAttribute{
+ Computed: true,
+ Description: "Clear cached ocspStapling response.",
+ },
+ "deletefromdevice": schema.BoolAttribute{
+ Computed: true,
+ Description: "Delete cert/key file from file system.",
+ },
+ "deletecertkeyfilesonremoval": schema.StringAttribute{
+ Computed: true,
+ Description: "Delete certificate and key files when the certificate is removed.",
+ },
+ "passplain": schema.StringAttribute{
+ Computed: true,
+ Description: "Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format.",
+ },
+ "passplain_wo": schema.StringAttribute{
+ Computed: true,
+ Description: "Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format.",
+ },
+ "passplain_wo_version": schema.Int64Attribute{
+ Description: "Increment this version to signal a passplain_wo update.",
+ Computed: true,
+ },
+ },
+ }
+}
diff --git a/citrixadc_framework/sslcertkey/datasource_sslcertkey.go b/citrixadc_framework/sslcertkey/datasource_sslcertkey.go
new file mode 100644
index 000000000..d05cc34ef
--- /dev/null
+++ b/citrixadc_framework/sslcertkey/datasource_sslcertkey.go
@@ -0,0 +1,62 @@
+package sslcertkey
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/citrix/adc-nitro-go/service"
+
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+)
+
+var _ datasource.DataSource = (*SslcertkeyDataSource)(nil)
+
+func SslCertKeyDataSource() datasource.DataSource {
+ return &SslcertkeyDataSource{}
+}
+
+type SslcertkeyDataSource struct {
+ client *service.NitroClient
+}
+
+func (d *SslcertkeyDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_sslcertkey"
+}
+
+func (d *SslcertkeyDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+ d.client = *req.ProviderData.(**service.NitroClient)
+}
+
+func (d *SslcertkeyDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = SslCertKeyDataSourceSchema()
+}
+
+func (d *SslcertkeyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var data SslCertKeyResourceModel
+ // Read Terraform configuration data into the model
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get the certkey name from config
+ sslcertkeyName := data.Certkey.ValueString()
+
+ var getResponseData map[string]interface{}
+ var err error
+
+ getResponseData, err = d.client.FindResource(service.Sslcertkey.Type(), sslcertkeyName)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read sslcertkey %s, got error: %s", sslcertkeyName, err))
+ return
+ }
+
+ sslcertkeySetAttrFromGet(ctx, &data, getResponseData)
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/citrixadc_framework/sslcertkey/resource_schema.go b/citrixadc_framework/sslcertkey/resource_schema.go
new file mode 100644
index 000000000..da64dd6a7
--- /dev/null
+++ b/citrixadc_framework/sslcertkey/resource_schema.go
@@ -0,0 +1,278 @@
+package sslcertkey
+
+import (
+ "context"
+
+ "github.com/citrix/adc-nitro-go/resource/config/ssl"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+
+ "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework/utils"
+)
+
+// SslCertKeyResourceModel describes the resource data model.
+type SslCertKeyResourceModel struct {
+ Id types.String `tfsdk:"id"`
+ Certkey types.String `tfsdk:"certkey"`
+ Cert types.String `tfsdk:"cert"`
+ Key types.String `tfsdk:"key"`
+ Password types.Bool `tfsdk:"password"`
+ Fipskey types.String `tfsdk:"fipskey"`
+ Hsmkey types.String `tfsdk:"hsmkey"`
+ Inform types.String `tfsdk:"inform"`
+ Expirymonitor types.String `tfsdk:"expirymonitor"`
+ NotificationPeriod types.Int64 `tfsdk:"notificationperiod"`
+ Bundle types.String `tfsdk:"bundle"`
+ LinkCertKeyName types.String `tfsdk:"linkcertkeyname"`
+ NoDomainCheck types.Bool `tfsdk:"nodomaincheck"`
+ OcspStaplingCache types.Bool `tfsdk:"ocspstaplingcache"`
+ DeleteFromDevice types.Bool `tfsdk:"deletefromdevice"`
+ DeleteCertKeyFilesOnRemoval types.String `tfsdk:"deletecertkeyfilesonremoval"`
+ Passplain types.String `tfsdk:"passplain"`
+ PassplainWo types.String `tfsdk:"passplain_wo"`
+ PassplainWoVersion types.Int64 `tfsdk:"passplain_wo_version"`
+}
+
+func (r *SslCertKeyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Version: 1,
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: "The ID of the SSL certificate key pair. This is the same as the certkey attribute.",
+ },
+ "certkey": schema.StringAttribute{
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Description: "Name for the certificate and private-key pair. Must begin with an ASCII alphanumeric or underscore (_) character, and must contain only ASCII alphanumeric, underscore, hash (#), period (.), space, colon (:), at (@), equals (=), and hyphen (-) characters. The following fields cannot be changed after creation: certkey, bundle, hsmkey.",
+ },
+ "cert": schema.StringAttribute{
+ Required: true,
+ Description: "Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. Storing a certificate in any location other than the default might cause inconsistency in a high availability setup. /nsconfig/ssl/ is the default path.",
+ },
+ "key": schema.StringAttribute{
+ Optional: true,
+ Description: "Name of and, optionally, path to the private-key file that is used to form the certificate-key pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. Storing a certificate in any location other than the default might cause inconsistency in a high availability setup. /nsconfig/ssl/ is the default path.",
+ },
+ "password": schema.BoolAttribute{
+ Optional: true,
+ Description: "Passphrase that was used to encrypt the private-key. Use this option to load encrypted private-keys in PEM format.",
+ },
+ "fipskey": schema.StringAttribute{
+ Optional: true,
+ Description: "Name of the FIPS key that was created inside the Hardware Security Module (HSM) of a FIPS appliance, or a key that was imported into the HSM.",
+ },
+ "hsmkey": schema.StringAttribute{
+ Optional: true,
+ Description: "Name of the HSM key that was created in the External Hardware Security Module (HSM) of a FIPS appliance. The following fields cannot be changed after creation: certkey, bundle, hsmkey.",
+ },
+ "inform": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ Description: "Input format of the certificate and the private-key files. The three formats supported by the appliance are: PEM - Privacy Enhanced Mail, DER - Distinguished Encoding Rule, PFX - Personal Information Exchange. Default: PEM",
+ },
+ "expirymonitor": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ Description: "Issue an alert when the certificate is about to expire. Possible values: ENABLED, DISABLED",
+ },
+ "notificationperiod": schema.Int64Attribute{
+ Optional: true,
+ Computed: true,
+ Description: "Time, in number of days, before certificate expiration, at which to generate an alert that the certificate is about to expire. Minimum value: 10, Maximum value: 100",
+ },
+ "bundle": schema.StringAttribute{
+ Optional: true,
+ Description: "Parse the certificate chain as a single file after linking the server certificate to its issuer's certificate within the file. Possible values: YES, NO. The following fields cannot be changed after creation: certkey, bundle, hsmkey.",
+ },
+ "linkcertkeyname": schema.StringAttribute{
+ Optional: true,
+ Description: "Name of the Certificate Authority certificate-key pair to which to link a certificate-key pair.",
+ },
+ "nodomaincheck": schema.BoolAttribute{
+ Optional: true,
+ Description: "Override the check for matching domain names during a certificate update operation.",
+ },
+ "ocspstaplingcache": schema.BoolAttribute{
+ Optional: true,
+ Description: "Clear cached ocspStapling response in case of an update operation.",
+ },
+ "deletefromdevice": schema.BoolAttribute{
+ Optional: true,
+ Description: "Delete cert/key file from file system. Possible values: true, false",
+ },
+ "deletecertkeyfilesonremoval": schema.StringAttribute{
+ Optional: true,
+ Computed: true,
+ Description: "Delete certificate and key files when the certificate is removed. Possible values: YES, NO",
+ },
+ "passplain": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ Description: "Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format.",
+ },
+ "passplain_wo": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ WriteOnly: true,
+ Description: "Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format.",
+ },
+ "passplain_wo_version": schema.Int64Attribute{
+ Description: "Increment this version to signal a passplain_wo update.",
+ Optional: true,
+ Computed: true,
+ Default: int64default.StaticInt64(1),
+ },
+ },
+ }
+}
+
+func sslcertkeyGetThePayloadFromtheConfig(ctx context.Context, data *SslCertKeyResourceModel) ssl.Sslcertkey {
+ tflog.Debug(ctx, "In sslcertkeyGetThePayloadFromtheConfig Function")
+
+ // Create API request body from the model
+ sslcertkey := ssl.Sslcertkey{}
+
+ if !data.Certkey.IsNull() {
+ sslcertkey.Certkey = data.Certkey.ValueString()
+ }
+ if !data.Cert.IsNull() {
+ sslcertkey.Cert = data.Cert.ValueString()
+ }
+ if !data.Key.IsNull() {
+ sslcertkey.Key = data.Key.ValueString()
+ }
+ if !data.Password.IsNull() {
+ sslcertkey.Password = data.Password.ValueBool()
+ }
+ if !data.Fipskey.IsNull() {
+ sslcertkey.Fipskey = data.Fipskey.ValueString()
+ }
+ if !data.Hsmkey.IsNull() {
+ sslcertkey.Hsmkey = data.Hsmkey.ValueString()
+ }
+ if !data.Inform.IsNull() {
+ sslcertkey.Inform = data.Inform.ValueString()
+ }
+ if !data.Passplain.IsNull() {
+ sslcertkey.Passplain = data.Passplain.ValueString()
+ }
+ if !data.Expirymonitor.IsNull() {
+ sslcertkey.Expirymonitor = data.Expirymonitor.ValueString()
+ }
+ if !data.NotificationPeriod.IsNull() {
+ sslcertkey.Notificationperiod = utils.IntPtr(int(data.NotificationPeriod.ValueInt64()))
+ }
+ if !data.Bundle.IsNull() {
+ sslcertkey.Bundle = data.Bundle.ValueString()
+ }
+ if !data.NoDomainCheck.IsNull() {
+ sslcertkey.Nodomaincheck = data.NoDomainCheck.ValueBool()
+ }
+ if !data.OcspStaplingCache.IsNull() {
+ sslcertkey.Ocspstaplingcache = data.OcspStaplingCache.ValueBool()
+ }
+ if !data.DeleteCertKeyFilesOnRemoval.IsNull() {
+ sslcertkey.Deletecertkeyfilesonremoval = data.DeleteCertKeyFilesOnRemoval.ValueString()
+ }
+ if !data.PassplainWo.IsNull() {
+ passplainWo := data.PassplainWo.ValueString()
+ if passplainWo != "" {
+ sslcertkey.Passplain = passplainWo
+ }
+ }
+
+ return sslcertkey
+}
+
+func sslcertkeySetAttrFromGet(ctx context.Context, data *SslCertKeyResourceModel, getResponseData map[string]interface{}) *SslCertKeyResourceModel {
+ tflog.Debug(ctx, "In sslcertkeySetAttrFromGet Function")
+
+ // Set ID for the resource - use certkey as the identifier
+ if val, ok := getResponseData["certkey"]; ok && val != nil {
+ data.Id = types.StringValue(val.(string))
+ data.Certkey = types.StringValue(val.(string))
+ }
+
+ // Convert API response to model
+ if val, ok := getResponseData["cert"]; ok && val != nil {
+ data.Cert = types.StringValue(val.(string))
+ } else {
+ data.Cert = types.StringNull()
+ }
+ if val, ok := getResponseData["key"]; ok && val != nil {
+ data.Key = types.StringValue(val.(string))
+ } else {
+ data.Key = types.StringNull()
+ }
+ // Password and passplain are not returned by NITRO API - keep existing state
+ // The API returns hashed values which would cause drift, so we skip updating these fields
+ // They will retain their configured values from the plan
+ if val, ok := getResponseData["fipskey"]; ok && val != nil {
+ data.Fipskey = types.StringValue(val.(string))
+ } else {
+ data.Fipskey = types.StringNull()
+ }
+ if val, ok := getResponseData["hsmkey"]; ok && val != nil {
+ data.Hsmkey = types.StringValue(val.(string))
+ } else {
+ data.Hsmkey = types.StringNull()
+ }
+ if val, ok := getResponseData["inform"]; ok && val != nil {
+ data.Inform = types.StringValue(val.(string))
+ } else {
+ data.Inform = types.StringNull()
+ }
+ if val, ok := getResponseData["expirymonitor"]; ok && val != nil {
+ data.Expirymonitor = types.StringValue(val.(string))
+ } else {
+ data.Expirymonitor = types.StringNull()
+ }
+ if val, ok := getResponseData["notificationperiod"]; ok && val != nil {
+ if intVal, err := utils.ConvertToInt64(val); err == nil {
+ data.NotificationPeriod = types.Int64Value(intVal)
+ }
+ } else {
+ data.NotificationPeriod = types.Int64Null()
+ }
+ if val, ok := getResponseData["bundle"]; ok && val != nil {
+ data.Bundle = types.StringValue(val.(string))
+ } else {
+ data.Bundle = types.StringNull()
+ }
+ if val, ok := getResponseData["linkcertkeyname"]; ok && val != nil {
+ data.LinkCertKeyName = types.StringValue(val.(string))
+ } else {
+ data.LinkCertKeyName = types.StringNull()
+ }
+ // if val, ok := getResponseData["nodomaincheck"]; ok && val != nil {
+ // data.NoDomainCheck = types.BoolValue(val.(bool))
+ // } else {
+ // data.NoDomainCheck = types.BoolNull()
+ // }
+ if val, ok := getResponseData["ocspstaplingcache"]; ok && val != nil {
+ data.OcspStaplingCache = types.BoolValue(val.(bool))
+ } else {
+ data.OcspStaplingCache = types.BoolNull()
+ }
+ if val, ok := getResponseData["deletecertkeyfilesonremoval"]; ok && val != nil {
+ data.DeleteCertKeyFilesOnRemoval = types.StringValue(val.(string))
+ } else {
+ data.DeleteCertKeyFilesOnRemoval = types.StringNull()
+ }
+ if val, ok := getResponseData["deletefromdevice"]; ok && val != nil {
+ data.DeleteFromDevice = types.BoolValue(val.(bool))
+ } else {
+ data.DeleteFromDevice = types.BoolNull()
+ }
+
+ return data
+}
diff --git a/citrixadc_framework/sslcertkey/resource_sslcertkey.go b/citrixadc_framework/sslcertkey/resource_sslcertkey.go
new file mode 100644
index 000000000..42d36751d
--- /dev/null
+++ b/citrixadc_framework/sslcertkey/resource_sslcertkey.go
@@ -0,0 +1,430 @@
+package sslcertkey
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/citrix/adc-nitro-go/resource/config/ssl"
+ "github.com/citrix/adc-nitro-go/service"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+
+ "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework/utils"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ resource.Resource = &SslCertKeyResource{}
+var _ resource.ResourceWithConfigure = (*SslCertKeyResource)(nil)
+var _ resource.ResourceWithImportState = (*SslCertKeyResource)(nil)
+
+func NewSslCertKeyResource() resource.Resource {
+ return &SslCertKeyResource{}
+}
+
+// SslCertKeyResource defines the resource implementation.
+type SslCertKeyResource struct {
+ client *service.NitroClient
+}
+
+func (r *SslCertKeyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
+
+func (r *SslCertKeyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_sslcertkey"
+}
+
+func (r *SslCertKeyResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+ // Set the client for the resource.
+ r.client = *req.ProviderData.(**service.NitroClient)
+}
+
+func (r *SslCertKeyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data SslCertKeyResourceModel
+ var config SslCertKeyResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+ // Read config to access write-only attributes (like passplain)
+ resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Creating sslcertkey resource")
+
+ // Get certkey name - if not provided, generate one
+ var sslcertkeyName string
+ if data.Certkey.IsNull() || data.Certkey.ValueString() == "" {
+ sslcertkeyName = "tf-sslcertkey-" + fmt.Sprintf("%d", len(data.Cert.ValueString()))
+ data.Certkey = types.StringValue(sslcertkeyName)
+ } else {
+ sslcertkeyName = data.Certkey.ValueString()
+ }
+
+ // Use config (not plan) to get write-only attributes like passplain
+ sslcertkey := sslcertkeyGetThePayloadFromtheConfig(ctx, &config)
+
+ // Nodomaincheck is always set to false on creation
+ sslcertkey.Nodomaincheck = false
+
+ // Make API call
+ _, err := r.client.AddResource(service.Sslcertkey.Type(), sslcertkeyName, &sslcertkey)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create sslcertkey, got error: %s", err))
+ return
+ }
+
+ data.Id = types.StringValue(sslcertkeyName)
+
+ tflog.Trace(ctx, "Created sslcertkey resource")
+
+ // Handle linked certificate if configured
+ if !data.LinkCertKeyName.IsNull() && data.LinkCertKeyName.ValueString() != "" {
+ if err := r.handleLinkedCertificate(ctx, &data, &resp.Diagnostics); err != nil {
+ tflog.Error(ctx, "Error linking certificate during creation")
+ // If linking fails, delete the created certificate
+ delErr := r.client.DeleteResource(service.Sslcertkey.Type(), sslcertkeyName)
+ if delErr != nil {
+ resp.Diagnostics.AddError("Cleanup Error",
+ fmt.Sprintf("Failed to delete certificate after link error. Link error: %s, Delete error: %s", err, delErr))
+ } else {
+ resp.Diagnostics.AddError("Client Error",
+ fmt.Sprintf("Failed to link certificate: %s. Certificate has been deleted.", err))
+ }
+ return
+ }
+ }
+
+ // Read the updated state back
+ r.readSslCertKeyFromApi(ctx, &data, &resp.Diagnostics)
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *SslCertKeyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data SslCertKeyResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Reading sslcertkey resource")
+
+ r.readSslCertKeyFromApi(ctx, &data, &resp.Diagnostics)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *SslCertKeyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan, state, config SslCertKeyResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Updating sslcertkey resource")
+
+ sslcertkeyName := plan.Certkey.ValueString()
+
+ // Determine which type of update is needed
+ needsUpdate := false
+ needsChange := false
+ needsClear := false
+
+ sslcertkeyUpdate := ssl.Sslcertkey{
+ Certkey: sslcertkeyName,
+ }
+ sslcertkeyChange := ssl.Sslcertkey{
+ Certkey: sslcertkeyName,
+ }
+ sslcertkeyClear := ssl.Sslcertkey{
+ Certkey: sslcertkeyName,
+ }
+
+ // Check for changes that require Update API
+ if !plan.Expirymonitor.Equal(state.Expirymonitor) {
+ tflog.Debug(ctx, "Expirymonitor has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyUpdate.Expirymonitor = plan.Expirymonitor.ValueString()
+ needsUpdate = true
+ }
+ if !plan.NotificationPeriod.Equal(state.NotificationPeriod) {
+ tflog.Debug(ctx, "Notificationperiod has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ if !plan.NotificationPeriod.IsNull() {
+ sslcertkeyUpdate.Notificationperiod = utils.IntPtr(int(plan.NotificationPeriod.ValueInt64()))
+ }
+ needsUpdate = true
+ }
+ if !plan.DeleteCertKeyFilesOnRemoval.Equal(state.DeleteCertKeyFilesOnRemoval) {
+ tflog.Debug(ctx, "DeleteCertKeyFilesOnRemoval has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyUpdate.Deletecertkeyfilesonremoval = plan.DeleteCertKeyFilesOnRemoval.ValueString()
+ needsUpdate = true
+ }
+
+ // Check for changes that require Change API
+ if !plan.Cert.Equal(state.Cert) {
+ tflog.Debug(ctx, "Cert has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyChange.Cert = plan.Cert.ValueString()
+ needsChange = true
+ }
+ if !plan.Key.Equal(state.Key) {
+ tflog.Debug(ctx, "Key has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyChange.Key = plan.Key.ValueString()
+ needsChange = true
+ }
+ if !plan.Password.Equal(state.Password) {
+ tflog.Debug(ctx, "Password has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyChange.Password = plan.Password.ValueBool()
+ needsChange = true
+ }
+ if !plan.Fipskey.Equal(state.Fipskey) {
+ tflog.Debug(ctx, "Fipskey has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyChange.Fipskey = plan.Fipskey.ValueString()
+ needsChange = true
+ }
+ if !plan.Inform.Equal(state.Inform) {
+ tflog.Debug(ctx, "Inform has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyChange.Inform = plan.Inform.ValueString()
+ needsChange = true
+ }
+ if !plan.Passplain.Equal(state.Passplain) || !plan.PassplainWoVersion.Equal(state.PassplainWoVersion) {
+ tflog.Debug(ctx, "PassplainWoVersion has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+
+ sslcertkeyChange.Passplain = config.Passplain.ValueString()
+ // Use config value since passplain is WriteOnly
+ if !config.PassplainWo.IsNull() {
+ passplainWo := config.PassplainWo.ValueString()
+ if passplainWo != "" {
+ sslcertkeyChange.Passplain = config.PassplainWo.ValueString()
+ }
+ }
+ needsChange = true
+ }
+
+ // Check for changes that require Clear API
+ if !config.OcspStaplingCache.IsNull() && !plan.OcspStaplingCache.Equal(state.OcspStaplingCache) {
+ tflog.Debug(ctx, "OcspStaplingCache has changed for sslcertkey", map[string]interface{}{"certkey": sslcertkeyName})
+ sslcertkeyClear.Ocspstaplingcache = plan.OcspStaplingCache.ValueBool()
+ needsClear = true
+ }
+
+ // Execute Update API if needed
+ if needsUpdate {
+ // Expirymonitor is always expected by NITRO API
+ if !plan.Expirymonitor.IsNull() {
+ sslcertkeyUpdate.Expirymonitor = plan.Expirymonitor.ValueString()
+ }
+ _, err := r.client.UpdateResource(service.Sslcertkey.Type(), sslcertkeyName, &sslcertkeyUpdate)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update sslcertkey %s, got error: %s", sslcertkeyName, err))
+ return
+ }
+ }
+
+ // Execute Change API if needed
+ if needsChange {
+ // Nodomaincheck is a flag for the change operation
+ if !plan.NoDomainCheck.IsNull() {
+ sslcertkeyChange.Nodomaincheck = plan.NoDomainCheck.ValueBool()
+ }
+ _, err := r.client.ChangeResource(service.Sslcertkey.Type(), sslcertkeyName, &sslcertkeyChange)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to change sslcertkey %s, got error: %s", sslcertkeyName, err))
+ return
+ }
+ }
+
+ // Execute Clear API if needed
+ if needsClear {
+ err := r.client.ActOnResource(service.Sslcertkey.Type(), &sslcertkeyClear, "clear")
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to clear sslcertkey %s, got error: %s", sslcertkeyName, err))
+ return
+ }
+ }
+
+ // Handle linked certificate changes
+ if err := r.handleLinkedCertificate(ctx, &plan, &resp.Diagnostics); err != nil {
+ tflog.Error(ctx, "Error linking certificate during update")
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to handle linked certificate for %s, got error: %s", sslcertkeyName, err))
+ return
+ }
+
+ tflog.Trace(ctx, "Updated sslcertkey resource")
+
+ // Ensure plan has the correct ID before reading from API
+ plan.Id = state.Id
+
+ // Read the updated state back - update plan with fresh API data
+ r.readSslCertKeyFromApi(ctx, &plan, &resp.Diagnostics)
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+}
+
+func (r *SslCertKeyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var data SslCertKeyResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "Deleting sslcertkey resource")
+
+ sslcertkeyName := data.Id.ValueString()
+
+ // Unlink certificate before deletion
+ if err := r.unlinkCertificate(ctx, &data, &resp.Diagnostics); err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to unlink certificate %s, got error: %s", sslcertkeyName, err))
+ return
+ }
+
+ // Build delete arguments
+ args := make([]string, 0)
+ if !data.DeleteFromDevice.IsNull() {
+ args = append(args, fmt.Sprintf("deletefromdevice:%t", data.DeleteFromDevice.ValueBool()))
+ }
+ if !data.DeleteCertKeyFilesOnRemoval.IsNull() {
+ args = append(args, fmt.Sprintf("deletecertkeyfilesonremoval:%s", data.DeleteCertKeyFilesOnRemoval.ValueString()))
+ }
+
+ // Make API call with arguments
+ err := r.client.DeleteResourceWithArgs(service.Sslcertkey.Type(), sslcertkeyName, args)
+ if err != nil {
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete sslcertkey %s, got error: %s", sslcertkeyName, err))
+ return
+ }
+
+ tflog.Trace(ctx, "Deleted sslcertkey resource")
+}
+
+// Helper function to read sslcertkey data from API
+func (r *SslCertKeyResource) readSslCertKeyFromApi(ctx context.Context, data *SslCertKeyResourceModel, diags *diag.Diagnostics) {
+ sslcertkeyName := data.Id.ValueString()
+
+ getResponseData, err := r.client.FindResource(service.Sslcertkey.Type(), sslcertkeyName)
+ if err != nil {
+ tflog.Warn(ctx, fmt.Sprintf("Clearing sslcertkey state %s", sslcertkeyName))
+ data.Id = types.StringNull()
+ return
+ }
+
+ sslcertkeySetAttrFromGet(ctx, data, getResponseData)
+}
+
+// Helper function to handle linked certificate
+func (r *SslCertKeyResource) handleLinkedCertificate(ctx context.Context, data *SslCertKeyResourceModel, diags *diag.Diagnostics) error {
+ tflog.Debug(ctx, "In handleLinkedCertificate")
+
+ sslcertkeyName := data.Certkey.ValueString()
+
+ // Get current state from API
+ getResponseData, err := r.client.FindResource(service.Sslcertkey.Type(), sslcertkeyName)
+ if err != nil {
+ tflog.Error(ctx, fmt.Sprintf("Error finding sslcertkey %s", sslcertkeyName))
+ data.Id = types.StringNull()
+ return err
+ }
+
+ // Get actual and configured linked certificate names
+ var actualLinkedCertKeyname interface{} = nil
+ if val, ok := getResponseData["linkcertkeyname"]; ok {
+ actualLinkedCertKeyname = val
+ }
+
+ configuredLinkedCertKeyname := ""
+ if !data.LinkCertKeyName.IsNull() {
+ configuredLinkedCertKeyname = data.LinkCertKeyName.ValueString()
+ }
+
+ // Check for noop conditions
+ if actualLinkedCertKeyname != nil && actualLinkedCertKeyname.(string) == configuredLinkedCertKeyname {
+ tflog.Debug(ctx, fmt.Sprintf("actual and configured linked certificates identical: %s", actualLinkedCertKeyname))
+ return nil
+ }
+
+ if actualLinkedCertKeyname == nil && configuredLinkedCertKeyname == "" {
+ tflog.Debug(ctx, "actual and configured linked certificates both empty")
+ return nil
+ }
+
+ // Unlink existing certificate if present
+ if err := r.unlinkCertificate(ctx, data, diags); err != nil {
+ return err
+ }
+
+ // Link new certificate if configured
+ if configuredLinkedCertKeyname != "" {
+ tflog.Debug(ctx, fmt.Sprintf("Linking certkey: %s", configuredLinkedCertKeyname))
+ sslCertkey := ssl.Sslcertkey{
+ Certkey: sslcertkeyName,
+ Linkcertkeyname: configuredLinkedCertKeyname,
+ }
+ if err := r.client.ActOnResource(service.Sslcertkey.Type(), &sslCertkey, "link"); err != nil {
+ tflog.Error(ctx, fmt.Sprintf("Error linking certificate: %v", err))
+ return err
+ }
+ } else {
+ tflog.Debug(ctx, "configured linked certkey is empty, nothing to do")
+ }
+
+ return nil
+}
+
+// Helper function to unlink certificate
+func (r *SslCertKeyResource) unlinkCertificate(ctx context.Context, data *SslCertKeyResourceModel, diags *diag.Diagnostics) error {
+ sslcertkeyName := data.Certkey.ValueString()
+ if sslcertkeyName == "" {
+ sslcertkeyName = data.Id.ValueString()
+ }
+
+ getResponseData, err := r.client.FindResource(service.Sslcertkey.Type(), sslcertkeyName)
+ if err != nil {
+ tflog.Error(ctx, fmt.Sprintf("Error finding sslcertkey %s", sslcertkeyName))
+ data.Id = types.StringNull()
+ return err
+ }
+
+ actualLinkedCertKeyname := getResponseData["linkcertkeyname"]
+
+ if actualLinkedCertKeyname != nil {
+ tflog.Debug(ctx, fmt.Sprintf("Unlinking certkey: %s", actualLinkedCertKeyname))
+
+ sslCertkey := ssl.Sslcertkey{
+ Certkey: sslcertkeyName,
+ }
+ if err := r.client.ActOnResource(service.Sslcertkey.Type(), &sslCertkey, "unlink"); err != nil {
+ tflog.Error(ctx, fmt.Sprintf("Error unlinking certificate: %v", err))
+ return err
+ }
+ } else {
+ tflog.Debug(ctx, "actual linked certkey is nil, nothing to do")
+ }
+
+ return nil
+}
diff --git a/citrixadc_framework/utils.go b/citrixadc_framework/utils.go
deleted file mode 100644
index 06f176a57..000000000
--- a/citrixadc_framework/utils.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package citrixadc_framework
-
-// intPtr returns a pointer to the provided int value
-// This is useful for optional fields in structs that require *int
-func intPtr(i int) *int {
- return &i
-}
-
-// boolPtr returns a pointer to the provided bool value
-// This is useful for optional fields in structs that require *bool
-func boolPtr(b bool) *bool {
- return &b
-}
diff --git a/citrixadc_framework/utils/utils.go b/citrixadc_framework/utils/utils.go
new file mode 100644
index 000000000..5e0562891
--- /dev/null
+++ b/citrixadc_framework/utils/utils.go
@@ -0,0 +1,32 @@
+package utils
+
+import (
+ "fmt"
+ "strconv"
+)
+
+// intPtr returns a pointer to the provided int value
+// This is useful for optional fields in structs that require *int
+func IntPtr(i int) *int {
+ return &i
+}
+
+// boolPtr returns a pointer to the provided bool value
+// This is useful for optional fields in structs that require *bool
+func BoolPtr(b bool) *bool {
+ return &b
+}
+
+// Helper function to convert interface{} to int64
+func ConvertToInt64(value interface{}) (int64, error) {
+ switch v := value.(type) {
+ case int:
+ return int64(v), nil
+ case int64:
+ return v, nil
+ case string:
+ return strconv.ParseInt(v, 10, 64)
+ default:
+ return 0, fmt.Errorf("cannot convert %T to int64", value)
+ }
+}
diff --git a/docs/data-sources/sslcertkey.md b/docs/data-sources/sslcertkey.md
new file mode 100644
index 000000000..6c7cdae76
--- /dev/null
+++ b/docs/data-sources/sslcertkey.md
@@ -0,0 +1,65 @@
+---
+subcategory: "SSL"
+---
+
+# Data Source `sslcertkey`
+
+The sslcertkey data source allows you to retrieve information about the TLS certificate keys.
+
+
+## Example usage
+
+```terraform
+data "citrixadc_sslcertkey" "tf_sslcertkey" {
+ certkey = "servercert1"
+}
+
+output "cert" {
+ value = data.citrixadc_sslcertkey.tf_sslcertkey.cert
+}
+
+output "key" {
+ value = data.citrixadc_sslcertkey.tf_sslcertkey.key
+}
+```
+
+
+## Argument Reference
+
+* `certkey` - (Required) Name for the certificate and private-key pair.
+
+## Attribute Reference
+
+In addition to the arguments, the following attributes are available:
+
+* `cert` - Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. Storing a certificate in any location other than the default might cause inconsistency in a high availability setup. /nsconfig/ssl/ is the default path.
+* `key` - Name of and, optionally, path to the private-key file that is used to form the certificate-key pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. Storing a certificate in any location other than the default might cause inconsistency in a high availability setup. /nsconfig/ssl/ is the default path.
+* `password` - Passphrase that was used to encrypt the private-key. Use this option to load encrypted private-keys in PEM format.
+* `fipskey` - Name of the FIPS key that was created inside the Hardware Security Module (HSM) of a FIPS appliance, or a key that was imported into the HSM.
+* `hsmkey` - Name of the HSM key that was created in the External Hardware Security Module (HSM) of a FIPS appliance.
+* `inform` - Input format of the certificate and the private-key files. The three formats supported by the appliance are: PEM - Privacy Enhanced Mail DER - Distinguished Encoding Rule PFX - Personal Information Exchange. Possible values: [ DER, PEM, PFX ]
+* `passplain` - Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format.
+* `expirymonitor` - Issue an alert when the certificate is about to expire. Possible values: [ ENABLED, DISABLED ]
+* `notificationperiod` - Time, in number of days, before certificate expiration, at which to generate an alert that the certificate is about to expire.
+* `bundle` - Parse the certificate chain as a single file after linking the server certificate to its issuer's certificate within the file. Possible values: [ YES, NO ]
+* `linkcertkeyname` - Name of the Certificate Authority certificate-key pair to which to link a certificate-key pair.
+* `nodomaincheck` - Override the check for matching domain names during a certificate update operation.
+* `ocspstaplingcache` - Clear cached ocspStapling response in certkey.
+* `deletecertkeyfilesonremoval` - This option is used to automatically delete certificate/key files from physical device when the added certkey is removed. When deleteCertKeyFilesOnRemoval option is used at rm certkey command, it overwrites the deleteCertKeyFilesOnRemoval setting used at add/set certkey command
+* `deletefromdevice` - Delete cert/key file from file system.
+
+
+## Attribute Reference
+
+In addition to the arguments, the following attributes are available:
+
+* `id` - The id of the sslcertkey. It has the same value as the `certkey` attribute.
+
+
+## Import
+
+A sslcertkey can be imported using its certkey, e.g.
+
+```shell
+terraform import citrixadc_sslcertkey.tf_sslcertkey tf_sslcertkey
+```
diff --git a/docs/resources/sslcertkey.md b/docs/resources/sslcertkey.md
index 0b8d3f742..8bc4f1484 100644
--- a/docs/resources/sslcertkey.md
+++ b/docs/resources/sslcertkey.md
@@ -7,7 +7,9 @@ subcategory: "SSL"
The sslcertkey resource is used to create TLS certificate keys.
-## Example usage
+## Example Usage
+
+### Basic SSL Certificate without Passphrase
```hcl
resource "citrixadc_sslcertkey" "tf_sslcertkey" {
@@ -19,17 +21,82 @@ resource "citrixadc_sslcertkey" "tf_sslcertkey" {
}
```
+### Using Legacy `passplain` (Backward Compatibility)
+
+The `passplain` attribute is maintained for backward compatibility but stores the passphrase in the state file:
+
+```hcl
+resource "citrixadc_sslcertkey" "tf_sslcertkey_legacy" {
+ certkey = "tf_sslcertkey_legacy"
+ cert = "/nsconfig/ssl/certificate1.crt"
+ key = "/nsconfig/ssl/encrypted_key1.pem"
+
+ # Legacy approach (passphrase stored in state file)
+ passplain = "my-secret-passphrase"
+}
+```
+
+### SSL Certificate with Ephemeral Passphrase Support
+
+This example demonstrates using `passplain_wo` (write-only) for enhanced security. The passphrase is not stored in the Terraform state file.
+
+```hcl
+variable "sslcertkey_passplain_wo" {
+ type = string
+ sensitive = true
+ description = "Passphrase for encrypted private key (not stored in state)"
+}
+
+resource "citrixadc_sslcertkey" "tf_sslcertkey_encrypted" {
+ certkey = "tf_sslcertkey_encrypted"
+ cert = "/nsconfig/ssl/certificate1.crt"
+ key = "/nsconfig/ssl/encrypted_key1.pem"
+ notificationperiod = 40
+ expirymonitor = "ENABLED"
+
+ # Ephemeral passphrase (write-only, not stored in state)
+ passplain_wo = var.sslcertkey_passplain_wo
+ passplain_wo_version = 1
+}
+```
+
+### Updating the Passphrase
+
+To update the passphrase for an encrypted private key, increment the `passplain_wo_version` value:
+
+```hcl
+variable "sslcertkey_passplain_wo" {
+ type = string
+ sensitive = true
+ description = "New passphrase for encrypted private key"
+}
+
+resource "citrixadc_sslcertkey" "tf_sslcertkey_encrypted" {
+ certkey = "tf_sslcertkey_encrypted"
+ cert = "/nsconfig/ssl/certificate1.crt"
+ key = "/nsconfig/ssl/encrypted_key1.pem"
+ notificationperiod = 40
+ expirymonitor = "ENABLED"
+
+ # Update passphrase by incrementing version
+ passplain_wo = var.sslcertkey_passplain_wo
+ passplain_wo_version = 2 # Incremented from 1 to trigger update
+}
+```
+
## Argument Reference
-* `certkey` - (Optional) Name for the certificate and private-key pair.
-* `cert` - (Optional) Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. Storing a certificate in any location other than the default might cause inconsistency in a high availability setup. /nsconfig/ssl/ is the default path.
+* `certkey` - (Required) Name for the certificate and private-key pair.
+* `cert` - (Required) Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. Storing a certificate in any location other than the default might cause inconsistency in a high availability setup. /nsconfig/ssl/ is the default path.
* `key` - (Optional) Name of and, optionally, path to the private-key file that is used to form the certificate-key pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. Storing a certificate in any location other than the default might cause inconsistency in a high availability setup. /nsconfig/ssl/ is the default path.
* `password` - (Optional) Passphrase that was used to encrypt the private-key. Use this option to load encrypted private-keys in PEM format.
* `fipskey` - (Optional) Name of the FIPS key that was created inside the Hardware Security Module (HSM) of a FIPS appliance, or a key that was imported into the HSM.
* `hsmkey` - (Optional) Name of the HSM key that was created in the External Hardware Security Module (HSM) of a FIPS appliance.
* `inform` - (Optional) Input format of the certificate and the private-key files. The three formats supported by the appliance are: PEM - Privacy Enhanced Mail DER - Distinguished Encoding Rule PFX - Personal Information Exchange. Possible values: [ DER, PEM, PFX ]
-* `passplain` - (Optional) Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format.
+* `passplain` - (Optional) Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format. **Note:** This value is stored in the Terraform state file. For enhanced security, use `passplain_wo` instead.
+* `passplain_wo` - (Optional, Write-Only) Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM format. **Recommended over `passplain`** for security reasons as this value is not stored in the Terraform state file. Must be used together with `passplain_wo_version`.
+* `passplain_wo_version` - (Optional) Version counter used to trigger updates when `passplain_wo` changes. Increment this value whenever you need to update the passphrase. Default: 1
* `expirymonitor` - (Optional) Issue an alert when the certificate is about to expire. Possible values: [ ENABLED, DISABLED ]
* `notificationperiod` - (Optional) Time, in number of days, before certificate expiration, at which to generate an alert that the certificate is about to expire.
* `bundle` - (Optional) Parse the certificate chain as a single file after linking the server certificate to its issuer's certificate within the file. Possible values: [ YES, NO ]
@@ -39,6 +106,29 @@ resource "citrixadc_sslcertkey" "tf_sslcertkey" {
* `deletecertkeyfilesonremoval` - (Optional) This option is used to automatically delete certificate/key files from physical device when the added certkey is removed. When deleteCertKeyFilesOnRemoval option is used at rm certkey command, it overwrites the deleteCertKeyFilesOnRemoval setting used at add/set certkey command
* `deletefromdevice` - (Optional) Delete cert/key file from file system.
+## Ephemeral Passphrase Support
+
+The `passplain_wo` (write-only) and `passplain_wo_version` attributes provide ephemeral passphrase support for enhanced security:
+
+### Why Use Ephemeral Passphrases?
+
+- **Security**: The passphrase is never stored in the Terraform state file
+- **Compliance**: Meets security requirements that prohibit storing secrets in state
+- **Best Practice**: Follows Terraform's recommended pattern for sensitive values that should not persist
+
+### How It Works
+
+1. **Initial Creation**: Provide the passphrase via `passplain_wo` and set `passplain_wo_version` to 1
+2. **Updating Passphrase**: Change the passphrase value and increment `passplain_wo_version` (e.g., to 2)
+3. **Version Tracking**: Terraform uses the version change to detect when the passphrase needs updating
+
+### Important Notes
+
+- `passplain_wo` and `passplain_wo_version` must be used together
+- The passphrase is sent to the Citrix ADC but never stored in Terraform state
+- For backward compatibility, `passplain` is still supported but stores the value in state
+- Do not use both `passplain` and `passplain_wo` simultaneously; choose one approach
+
## Attribute Reference
diff --git a/main.go b/main.go
index b16cf5d90..7b1216b14 100644
--- a/main.go
+++ b/main.go
@@ -7,7 +7,7 @@ import (
"log"
"github.com/citrix/terraform-provider-citrixadc/citrixadc"
- "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework"
+ "github.com/citrix/terraform-provider-citrixadc/citrixadc_framework/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
@@ -38,7 +38,7 @@ func main() {
}
// Create the Framework provider (already tf6)
- frameworkProviderFunc := providerserver.NewProtocol6(citrixadc_framework.New(version)())
+ frameworkProviderFunc := providerserver.NewProtocol6(provider.New(version)())
// Create the mux server
providers := []func() tfprotov6.ProviderServer{
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default/doc.go
new file mode 100644
index 000000000..13d2a1d93
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default/doc.go
@@ -0,0 +1,5 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+// Package int64default provides default values for types.Int64 attributes.
+package int64default
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default/static_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default/static_value.go
new file mode 100644
index 000000000..b01d2dcca
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default/static_value.go
@@ -0,0 +1,42 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package int64default
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// StaticInt64 returns a static int64 value default handler.
+//
+// Use StaticInt64 if a static default value for a int64 should be set.
+func StaticInt64(defaultVal int64) defaults.Int64 {
+ return staticInt64Default{
+ defaultVal: defaultVal,
+ }
+}
+
+// staticInt64Default is static value default handler that
+// sets a value on an int64 attribute.
+type staticInt64Default struct {
+ defaultVal int64
+}
+
+// Description returns a human-readable description of the default value handler.
+func (d staticInt64Default) Description(_ context.Context) string {
+ return fmt.Sprintf("value defaults to %d", d.defaultVal)
+}
+
+// MarkdownDescription returns a markdown description of the default value handler.
+func (d staticInt64Default) MarkdownDescription(_ context.Context) string {
+ return fmt.Sprintf("value defaults to `%d`", d.defaultVal)
+}
+
+// DefaultInt64 implements the static default value logic.
+func (d staticInt64Default) DefaultInt64(_ context.Context, req defaults.Int64Request, resp *defaults.Int64Response) {
+ resp.PlanValue = types.Int64Value(d.defaultVal)
+}
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/doc.go
new file mode 100644
index 000000000..6bbbb6607
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/doc.go
@@ -0,0 +1,5 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+// Package stringplanmodifier provides plan modifiers for types.String attributes.
+package stringplanmodifier
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace.go
new file mode 100644
index 000000000..e3adb4b97
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace.go
@@ -0,0 +1,30 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package stringplanmodifier
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+)
+
+// RequiresReplace returns a plan modifier that conditionally requires
+// resource replacement if:
+//
+// - The resource is planned for update.
+// - The plan and state values are not equal.
+//
+// Use RequiresReplaceIfConfigured if the resource replacement should
+// only occur if there is a configuration value (ignore unconfigured drift
+// detection changes). Use RequiresReplaceIf if the resource replacement
+// should check provider-defined conditional logic.
+func RequiresReplace() planmodifier.String {
+ return RequiresReplaceIf(
+ func(_ context.Context, _ planmodifier.StringRequest, resp *RequiresReplaceIfFuncResponse) {
+ resp.RequiresReplace = true
+ },
+ "If the value of this attribute changes, Terraform will destroy and recreate the resource.",
+ "If the value of this attribute changes, Terraform will destroy and recreate the resource.",
+ )
+}
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if.go
new file mode 100644
index 000000000..0afe6cebf
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if.go
@@ -0,0 +1,73 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package stringplanmodifier
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+)
+
+// RequiresReplaceIf returns a plan modifier that conditionally requires
+// resource replacement if:
+//
+// - The resource is planned for update.
+// - The plan and state values are not equal.
+// - The given function returns true. Returning false will not unset any
+// prior resource replacement.
+//
+// Use RequiresReplace if the resource replacement should always occur on value
+// changes. Use RequiresReplaceIfConfigured if the resource replacement should
+// occur on value changes, but only if there is a configuration value (ignore
+// unconfigured drift detection changes).
+func RequiresReplaceIf(f RequiresReplaceIfFunc, description, markdownDescription string) planmodifier.String {
+ return requiresReplaceIfModifier{
+ ifFunc: f,
+ description: description,
+ markdownDescription: markdownDescription,
+ }
+}
+
+// requiresReplaceIfModifier is an plan modifier that sets RequiresReplace
+// on the attribute if a given function is true.
+type requiresReplaceIfModifier struct {
+ ifFunc RequiresReplaceIfFunc
+ description string
+ markdownDescription string
+}
+
+// Description returns a human-readable description of the plan modifier.
+func (m requiresReplaceIfModifier) Description(_ context.Context) string {
+ return m.description
+}
+
+// MarkdownDescription returns a markdown description of the plan modifier.
+func (m requiresReplaceIfModifier) MarkdownDescription(_ context.Context) string {
+ return m.markdownDescription
+}
+
+// PlanModifyString implements the plan modification logic.
+func (m requiresReplaceIfModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
+ // Do not replace on resource creation.
+ if req.State.Raw.IsNull() {
+ return
+ }
+
+ // Do not replace on resource destroy.
+ if req.Plan.Raw.IsNull() {
+ return
+ }
+
+ // Do not replace if the plan and state values are equal.
+ if req.PlanValue.Equal(req.StateValue) {
+ return
+ }
+
+ ifFuncResp := &RequiresReplaceIfFuncResponse{}
+
+ m.ifFunc(ctx, req, ifFuncResp)
+
+ resp.Diagnostics.Append(ifFuncResp.Diagnostics...)
+ resp.RequiresReplace = ifFuncResp.RequiresReplace
+}
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if_configured.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if_configured.go
new file mode 100644
index 000000000..e1bf461dc
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if_configured.go
@@ -0,0 +1,34 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package stringplanmodifier
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+)
+
+// RequiresReplaceIfConfigured returns a plan modifier that conditionally requires
+// resource replacement if:
+//
+// - The resource is planned for update.
+// - The plan and state values are not equal.
+// - The configuration value is not null.
+//
+// Use RequiresReplace if the resource replacement should occur regardless of
+// the presence of a configuration value. Use RequiresReplaceIf if the resource
+// replacement should check provider-defined conditional logic.
+func RequiresReplaceIfConfigured() planmodifier.String {
+ return RequiresReplaceIf(
+ func(_ context.Context, req planmodifier.StringRequest, resp *RequiresReplaceIfFuncResponse) {
+ if req.ConfigValue.IsNull() {
+ return
+ }
+
+ resp.RequiresReplace = true
+ },
+ "If the value of this attribute is configured and changes, Terraform will destroy and recreate the resource.",
+ "If the value of this attribute is configured and changes, Terraform will destroy and recreate the resource.",
+ )
+}
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if_func.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if_func.go
new file mode 100644
index 000000000..bde13cb3f
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if_func.go
@@ -0,0 +1,25 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package stringplanmodifier
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+)
+
+// RequiresReplaceIfFunc is a conditional function used in the RequiresReplaceIf
+// plan modifier to determine whether the attribute requires replacement.
+type RequiresReplaceIfFunc func(context.Context, planmodifier.StringRequest, *RequiresReplaceIfFuncResponse)
+
+// RequiresReplaceIfFuncResponse is the response type for a RequiresReplaceIfFunc.
+type RequiresReplaceIfFuncResponse struct {
+ // Diagnostics report errors or warnings related to this logic. An empty
+ // or unset slice indicates success, with no warnings or errors generated.
+ Diagnostics diag.Diagnostics
+
+ // RequiresReplace should be enabled if the resource should be replaced.
+ RequiresReplace bool
+}
diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/use_state_for_unknown.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/use_state_for_unknown.go
new file mode 100644
index 000000000..a6d77962e
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier/use_state_for_unknown.go
@@ -0,0 +1,55 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package stringplanmodifier
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+)
+
+// UseStateForUnknown returns a plan modifier that copies a known prior state
+// value into the planned value. Use this when it is known that an unconfigured
+// value will remain the same after a resource update.
+//
+// To prevent Terraform errors, the framework automatically sets unconfigured
+// and Computed attributes to an unknown value "(known after apply)" on update.
+// Using this plan modifier will instead display the prior state value in the
+// plan, unless a prior plan modifier adjusts the value.
+func UseStateForUnknown() planmodifier.String {
+ return useStateForUnknownModifier{}
+}
+
+// useStateForUnknownModifier implements the plan modifier.
+type useStateForUnknownModifier struct{}
+
+// Description returns a human-readable description of the plan modifier.
+func (m useStateForUnknownModifier) Description(_ context.Context) string {
+ return "Once set, the value of this attribute in state will not change."
+}
+
+// MarkdownDescription returns a markdown description of the plan modifier.
+func (m useStateForUnknownModifier) MarkdownDescription(_ context.Context) string {
+ return "Once set, the value of this attribute in state will not change."
+}
+
+// PlanModifyString implements the plan modification logic.
+func (m useStateForUnknownModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
+ // Do nothing if there is no state (resource is being created).
+ if req.State.Raw.IsNull() {
+ return
+ }
+
+ // Do nothing if there is a known planned value.
+ if !req.PlanValue.IsUnknown() {
+ return
+ }
+
+ // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
+ if req.ConfigValue.IsUnknown() {
+ return
+ }
+
+ resp.PlanValue = req.StateValue
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 5c42962f2..cc999efcf 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -251,7 +251,9 @@ github.com/hashicorp/terraform-plugin-framework/resource
github.com/hashicorp/terraform-plugin-framework/resource/identityschema
github.com/hashicorp/terraform-plugin-framework/resource/schema
github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults
+github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default
github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier
+github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier
github.com/hashicorp/terraform-plugin-framework/schema/validator
github.com/hashicorp/terraform-plugin-framework/tfsdk
github.com/hashicorp/terraform-plugin-framework/types