diff --git a/Makefile b/Makefile index cbd3e9e..21b5b76 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,16 @@ HOSTNAME=github.com NAMESPACE=ably NAME=ably BINARY=terraform-provider-${NAME} -VERSION=0.4.3 -OS_ARCH=darwin_amd64 +VERSION=0.8.0 +OS ?= $(shell uname -s | tr '[:upper:]' '[:lower:]') +# Add error checking for supported architectures +ARCH_NATIVE := $(shell uname -m) +ARCH_MAPPED := $(shell echo "$(ARCH_NATIVE)" | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/' -e 's/armv7l/arm/') +ifneq ($(ARCH_MAPPED),$(filter amd64 arm64 arm,$(ARCH_MAPPED))) +$(error Unsupported architecture: $(ARCH_NATIVE)) +endif +ARCH ?= $(ARCH_MAPPED) +OS_ARCH=${OS}_${ARCH} default: install @@ -13,11 +21,13 @@ build: release: GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_darwin_amd64 + GOOS=darwin GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_darwin_arm64 GOOS=freebsd GOARCH=386 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_freebsd_386 GOOS=freebsd GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_freebsd_amd64 GOOS=freebsd GOARCH=arm go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_freebsd_arm GOOS=linux GOARCH=386 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_linux_386 GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_linux_amd64 + GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_linux_arm64 GOOS=linux GOARCH=arm go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_linux_arm GOOS=openbsd GOARCH=386 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_openbsd_386 GOOS=openbsd GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o ./bin/${BINARY}_${VERSION}_openbsd_amd64 diff --git a/docs/resources/ingress_rule_postgres_outbox.md b/docs/resources/ingress_rule_postgres_outbox.md new file mode 100644 index 0000000..c164064 --- /dev/null +++ b/docs/resources/ingress_rule_postgres_outbox.md @@ -0,0 +1,54 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ably_ingress_rule_postgres_outbox Resource - terraform-provider-ably" +subcategory: "" +description: |- + The ably_ingress_rule_postgres_outbox resource Use the Postgres database connector to distribute changes from your Postgres database to end users at scale. It enables you to distribute records using the outbox pattern to large numbers of subscribing clients, in realtime, as the changes occur. +--- + +# ably_ingress_rule_postgres_outbox (Resource) + +The `ably_ingress_rule_postgres_outbox` resource Use the Postgres database connector to distribute changes from your Postgres database to end users at scale. It enables you to distribute records using the outbox pattern to large numbers of subscribing clients, in realtime, as the changes occur. + + + + +## Schema + +### Required + +- `app_id` (String) The Ably application ID. +- `target` (Attributes) object (rule_source) (see [below for nested schema](#nestedatt--target)) + +### Optional + +- `status` (String) The status of the rule. Rules can be enabled or disabled. + +### Read-Only + +- `id` (String) The rule ID. + + +### Nested Schema for `target` + +Required: + +- `nodes_table_name` (String) Name for the nodes table. +- `nodes_table_schema` (String) Schema for the nodes table in your database to allow for operation as a cluster to provide fault tolerance. +- `outbox_table_name` (String) Name for the outbox table. +- `outbox_table_schema` (String) Schema for the outbox table in your database which allows for the reliable publication of an ordered sequence of change event messages over Ably. +- `primary_site` (String) The primary data center in which to run the integration rule. +- `ssl_mode` (String) + ssl_mode (String) Determines the level of protection provided by the SSL connection. Options are: + - prefer: Attempt SSL but allow non-SSL. + - require: Always use SSL but don't verify certificates. + - verify-ca: Verify server certificate is signed by a trusted CA. + - verify-full: Verify server certificate and hostname. + +Default: prefer. +- `url` (String) The URL for your Postgres database, for example postgres://user:password@example.com:5432/your-database-name. The associated user must have the correct privileges + +Optional: + +- `ssl_root_cert` (String) Optional. Specifies the SSL certificate authority (CA) certificates. Required if SSL mode is verify-ca or verify-full. + + diff --git a/examples/playground/ingress_rule_postgres_outbox.tf b/examples/playground/ingress_rule_postgres_outbox.tf new file mode 100644 index 0000000..a308de3 --- /dev/null +++ b/examples/playground/ingress_rule_postgres_outbox.tf @@ -0,0 +1,14 @@ +resource "ably_ingress_rule_postgres_outbox" "rule0" { + app_id = ably_app.app0.id + status = "enabled" + target = { + url = "postgres://${var.postgres_user}:${var.postgres_password}@${var.postgres_host}:5432/${var.postgres_db}" + outbox_table_schema = "public" + outbox_table_name = "outbox" + nodes_table_schema = "public" + nodes_table_name = "nodes" + ssl_mode = "prefer" + ssl_root_cert = "-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIUYO1Lomxzj7VRawWwEFiQht9OLpUwDQYJKoZIhvcNAQEL BQAwTDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMQ8wDQYDVQQHDAZX ...snip... TOfReTlUQzgpXRW5h3n2LVXbXQhPGcVitb88Cm2R8cxQwgB1VncM8yvmKhREo2tz 7Y+sUx6eIl4dlNl9kVrH1TD3EwwtGsjUNlFSZhg= -----END CERTIFICATE-----" + primary_site = "us-east-1-A" + } +} diff --git a/examples/playground/main.tf b/examples/playground/main.tf index 90dca68..4514775 100644 --- a/examples/playground/main.tf +++ b/examples/playground/main.tf @@ -2,8 +2,8 @@ terraform { required_providers { ably = { - source = "ably/ably" - version = ">=0.4.2" + source = "local/ably/ably" + version = "0.8.0" } } } diff --git a/examples/playground/variables.tf b/examples/playground/variables.tf new file mode 100644 index 0000000..8d90284 --- /dev/null +++ b/examples/playground/variables.tf @@ -0,0 +1,38 @@ +variable "mongodb_user" { + description = "MongoDB username" + sensitive = true + default = "test" +} + +variable "mongodb_password" { + description = "MongoDB password" + sensitive = true + default = "test" +} + +variable "mongodb_host" { + description = "MongoDB host" + default = "test" +} + +variable "postgres_user" { + description = "PostgreSQL username" + sensitive = true + default = "test" +} + +variable "postgres_password" { + description = "PostgreSQL password" + sensitive = true + default = "test" +} + +variable "postgres_host" { + description = "PostgreSQL host" + default = "test" +} + +variable "postgres_db" { + description = "PostgreSQL database name" + default = "test" +} diff --git a/internal/provider/ingress_rules.go b/internal/provider/ingress_rules.go index e163c03..3ca99f9 100644 --- a/internal/provider/ingress_rules.go +++ b/internal/provider/ingress_rules.go @@ -27,6 +27,17 @@ func GetPlanIngressRule(plan AblyIngressRule) ably_control_go.NewIngressRule { FullDocumentBeforeChange: t.FullDocumentBeforeChange, PrimarySite: t.PrimarySite, } + case *AblyIngressRuleTargetPostgresOutbox: + target = &ably_control_go.IngressPostgresOutboxTarget{ + Url: t.Url, + OutboxTableSchema: t.OutboxTableSchema, + OutboxTableName: t.OutboxTableName, + NodesTableSchema: t.NodesTableSchema, + NodesTableName: t.NodesTableName, + SslMode: t.SslMode, + SslRootCert: t.SslRootCert, + PrimarySite: t.PrimarySite, + } } rule_values := ably_control_go.NewIngressRule{ @@ -44,7 +55,6 @@ func GetIngressRuleResponse(ably_ingress_rule *ably_control_go.IngressRule, plan switch v := ably_ingress_rule.Target.(type) { case *ably_control_go.IngressMongoTarget: - resp_target = &AblyIngressRuleTargetMongo{ Url: v.Url, Database: v.Database, @@ -54,6 +64,17 @@ func GetIngressRuleResponse(ably_ingress_rule *ably_control_go.IngressRule, plan FullDocumentBeforeChange: v.FullDocumentBeforeChange, PrimarySite: v.PrimarySite, } + case *ably_control_go.IngressPostgresOutboxTarget: + resp_target = &AblyIngressRuleTargetPostgresOutbox{ + Url: v.Url, + OutboxTableSchema: v.OutboxTableSchema, + OutboxTableName: v.OutboxTableName, + NodesTableSchema: v.NodesTableSchema, + NodesTableName: v.NodesTableName, + SslMode: v.SslMode, + SslRootCert: v.SslRootCert, + PrimarySite: v.PrimarySite, + } } resp_rule := AblyIngressRule{ @@ -174,8 +195,8 @@ func ReadIngressRule[T any](r IngressRule, ctx context.Context, req tfsdk_resour return } resp.Diagnostics.AddError( - fmt.Sprintf("Error deleting Resource %s", r.Name()), - fmt.Sprintf("Could not delete resource %s, unexpected error: %s", r.Name(), err.Error()), + fmt.Sprintf("Error reading Resource %s", r.Name()), + fmt.Sprintf("Could not read resource %s, unexpected error: %s", r.Name(), err.Error()), ) return } diff --git a/internal/provider/models.go b/internal/provider/models.go index 7c6c898..090e8a8 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -106,6 +106,17 @@ type AblyIngressRuleTargetMongo struct { PrimarySite string `tfsdk:"primary_site"` } +type AblyIngressRuleTargetPostgresOutbox struct { + Url string `tfsdk:"url"` + OutboxTableSchema string `tfsdk:"outbox_table_schema"` + OutboxTableName string `tfsdk:"outbox_table_name"` + NodesTableSchema string `tfsdk:"nodes_table_schema"` + NodesTableName string `tfsdk:"nodes_table_name"` + SslMode string `tfsdk:"ssl_mode"` + SslRootCert string `tfsdk:"ssl_root_cert"` + PrimarySite string `tfsdk:"primary_site"` +} + // Ably Rule type AblyRuleSource struct { ChannelFilter types.String `tfsdk:"channel_filter"` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7e85e03..ff00c30 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -143,6 +143,7 @@ func (p *provider) Resources(context.Context) []func() tfsdk_resource.Resource { func() tfsdk_resource.Resource { return resourceRuleAmqp{p} }, func() tfsdk_resource.Resource { return resourceRuleAmqpExternal{p} }, func() tfsdk_resource.Resource { return resourceIngressRuleMongo{p} }, + func() tfsdk_resource.Resource { return resourceIngressRulePostgresOutbox{p} }, } } diff --git a/internal/provider/resource_ably_ingress_rule_postgres_outbox.go b/internal/provider/resource_ably_ingress_rule_postgres_outbox.go new file mode 100644 index 0000000..f81bedf --- /dev/null +++ b/internal/provider/resource_ably_ingress_rule_postgres_outbox.go @@ -0,0 +1,105 @@ +package ably_control + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + tfsdk_resource "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type resourceIngressRulePostgresOutbox struct { + p *provider +} + +// Get Rule Resource schema +func (r resourceIngressRulePostgresOutbox) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return GetIngressRuleSchema( + map[string]tfsdk.Attribute{ + "url": { + Type: types.StringType, + Required: true, + Description: "The URL for your Postgres database, for example postgres://user:password@example.com:5432/your-database-name. The associated user must have the correct privileges", + }, + "outbox_table_schema": { + Type: types.StringType, + Required: true, + Description: "Schema for the outbox table in your database which allows for the reliable publication of an ordered sequence of change event messages over Ably.", + }, + "outbox_table_name": { + Type: types.StringType, + Required: true, + Description: "Name for the outbox table.", + }, + "nodes_table_schema": { + Type: types.StringType, + Required: true, + Description: "Schema for the nodes table in your database to allow for operation as a cluster to provide fault tolerance.", + }, + "nodes_table_name": { + Type: types.StringType, + Required: true, + Description: "Name for the nodes table.", + }, + "ssl_mode": { + Type: types.StringType, + Required: true, + Description: `+ ssl_mode (String) Determines the level of protection provided by the SSL connection. Options are: + - prefer: Attempt SSL but allow non-SSL. + - require: Always use SSL but don't verify certificates. + - verify-ca: Verify server certificate is signed by a trusted CA. + - verify-full: Verify server certificate and hostname. + +Default: prefer.`, + }, + "ssl_root_cert": { + Type: types.StringType, + Optional: true, + Description: "Optional. Specifies the SSL certificate authority (CA) certificates. Required if SSL mode is verify-ca or verify-full.", + }, + "primary_site": { + Type: types.StringType, + Required: true, + Description: " The primary data center in which to run the integration rule.", + }, + }, + "The `ably_ingress_rule_postgres_outbox` resource Use the Postgres database connector to distribute changes from your Postgres database to end users at scale. It enables you to distribute records using the outbox pattern to large numbers of subscribing clients, in realtime, as the changes occur."), nil +} + +func (r resourceIngressRulePostgresOutbox) Metadata(ctx context.Context, req tfsdk_resource.MetadataRequest, resp *tfsdk_resource.MetadataResponse) { + resp.TypeName = "ably_ingress_rule_postgres_outbox" +} + +func (r *resourceIngressRulePostgresOutbox) Provider() *provider { + return r.p +} + +func (r *resourceIngressRulePostgresOutbox) Name() string { + return "PostgresOutbox" +} + +// Create a new resource +func (r resourceIngressRulePostgresOutbox) Create(ctx context.Context, req tfsdk_resource.CreateRequest, resp *tfsdk_resource.CreateResponse) { + CreateIngressRule[AblyIngressRuleTargetPostgresOutbox](&r, ctx, req, resp) +} + +// Read resource +func (r resourceIngressRulePostgresOutbox) Read(ctx context.Context, req tfsdk_resource.ReadRequest, resp *tfsdk_resource.ReadResponse) { + ReadIngressRule[AblyIngressRuleTargetPostgresOutbox](&r, ctx, req, resp) +} + +// Update resource +func (r resourceIngressRulePostgresOutbox) Update(ctx context.Context, req tfsdk_resource.UpdateRequest, resp *tfsdk_resource.UpdateResponse) { + UpdateIngressRule[AblyIngressRuleTargetPostgresOutbox](&r, ctx, req, resp) +} + +// Delete resource +func (r resourceIngressRulePostgresOutbox) Delete(ctx context.Context, req tfsdk_resource.DeleteRequest, resp *tfsdk_resource.DeleteResponse) { + DeleteIngressRule[AblyIngressRuleTargetPostgresOutbox](&r, ctx, req, resp) +} + +// Import resource +func (r resourceIngressRulePostgresOutbox) ImportState(ctx context.Context, req tfsdk_resource.ImportStateRequest, resp *tfsdk_resource.ImportStateResponse) { + ImportResource(ctx, req, resp, "app_id", "id") +} diff --git a/internal/provider/resource_ably_ingress_rule_postgres_outbox_test.go b/internal/provider/resource_ably_ingress_rule_postgres_outbox_test.go new file mode 100644 index 0000000..532ee2a --- /dev/null +++ b/internal/provider/resource_ably_ingress_rule_postgres_outbox_test.go @@ -0,0 +1,126 @@ +package ably_control + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "testing" +) + +func TestAccAblyIngressRulePostgresOutbox(t *testing.T) { + app_name := acctest.RandStringFromCharSet(15, acctest.CharSetAlphaNum) + update_app_name := "acc-test-" + app_name + test_postgres_url := "postgres://test:test@test.com:5432/your-database-name" + test_update_postgres_url := "postgres://test:test@test1.com:5432/your-database-name" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create and Read testing of ably_app.app0 + { + Config: testAccAblyIngressRulePostgresOutboxConfig( + app_name, + "enabled", + test_postgres_url, + "public", + "outbox", + "public", + "nodes", + "prefer", + "-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIUYO1Lomxzj7VRawWwEFiQht9OLpUwDQYJKoZIhvcNAQEL BQAwTDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMQ8wDQYDVQQHDAZX ...snip... TOfReTlUQzgpXRW5h3n2LVXbXQhPGcVitb88Cm2R8cxQwgB1VncM8yvmKhREo2tz 7Y+sUx6eIl4dlNl9kVrH1TD3EwwtGsjUNlFSZhg= -----END CERTIFICATE-----", + "us-east-1-A", + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ably_app.app0", "name", app_name), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "status", "enabled"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.url", test_postgres_url), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.outbox_table_schema", "public"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.outbox_table_name", "outbox"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.nodes_table_schema", "public"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.nodes_table_name", "nodes"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.ssl_mode", "prefer"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.ssl_root_cert", "-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIUYO1Lomxzj7VRawWwEFiQht9OLpUwDQYJKoZIhvcNAQEL BQAwTDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMQ8wDQYDVQQHDAZX ...snip... TOfReTlUQzgpXRW5h3n2LVXbXQhPGcVitb88Cm2R8cxQwgB1VncM8yvmKhREo2tz 7Y+sUx6eIl4dlNl9kVrH1TD3EwwtGsjUNlFSZhg= -----END CERTIFICATE-----"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.primary_site", "us-east-1-A"), + ), + }, + // Update and Read testing of ably_app.app0 + { + Config: testAccAblyIngressRulePostgresOutboxConfig( + update_app_name, + "enabled", + test_update_postgres_url, + "public1", + "outbox1", + "public1", + "nodes1", + "verify-ca", + "-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIUYO1Lomxzj7VRawWwEFiQht9OLpUwDQYJKoZIhvcNAQEL BQAwTDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMQ8wDQYDVQQHDAZX ...snip... TOfReTlUQzgpXRW5h3n2LVXbXQhPGcVitb88Cm2R8cxQwgB1VncM8yvmKhREo2tz 7Y+sUx6eIl4dlNl9kVrH1TD3EwwtGsjUNlFSZhg= -----END CERTIFICATE-----", + "eu-west-1-A", + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ably_app.app0", "name", update_app_name), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "status", "enabled"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.url", test_update_postgres_url), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.outbox_table_schema", "public1"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.outbox_table_name", "outbox1"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.nodes_table_schema", "public1"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.nodes_table_name", "nodes1"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.ssl_mode", "verify-ca"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.ssl_root_cert", "-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIUYO1Lomxzj7VRawWwEFiQht9OLpUwDQYJKoZIhvcNAQEL BQAwTDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMQ8wDQYDVQQHDAZX ...snip... TOfReTlUQzgpXRW5h3n2LVXbXQhPGcVitb88Cm2R8cxQwgB1VncM8yvmKhREo2tz 7Y+sUx6eIl4dlNl9kVrH1TD3EwwtGsjUNlFSZhg= -----END CERTIFICATE-----"), + resource.TestCheckResourceAttr("ably_ingress_rule_postgres_outbox.rule0", "target.primary_site", "eu-west-1-A"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +// Function with inline HCL to provision an ably_app resource +func testAccAblyIngressRulePostgresOutboxConfig( + appName string, + ruleStatus string, + url string, + outboxTableSchema string, + outboxTableName string, + nodesTableSchema string, + nodesTableName string, + sslMode string, + sslRootCert string, + primarySite string, +) string { + return fmt.Sprintf(` +terraform { + required_providers { + ably = { + source = "github.com/ably/ably" + } + } +} + +# You can provide your Ably Token & URL inline or use environment variables ABLY_ACCOUNT_TOKEN & ABLY_URL +provider "ably" {} + +resource "ably_app" "app0" { + name = %[1]q + status = "enabled" + tls_only = true +} + +resource "ably_ingress_rule_postgres_outbox" "rule0" { + app_id = ably_app.app0.id + status = %[2]q + + target = { + url = %[3]q + outbox_table_schema = %[4]q + outbox_table_name = %[5]q + nodes_table_schema = %[6]q + nodes_table_name = %[7]q + ssl_mode = %[8]q + ssl_root_cert = %[9]q + primary_site = %[10]q + } + } +`, appName, ruleStatus, url, outboxTableSchema, outboxTableName, nodesTableSchema, nodesTableName, sslMode, sslRootCert, primarySite) +}