diff --git a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go index 9e1c2ec5..1c7b7c90 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go @@ -78,35 +78,37 @@ func (r *loadBalancerDataSource) Configure(ctx context.Context, req datasource.C // Schema defines the schema for the data source. func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ - "main": "Load Balancer data source schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"`name`\".", - "project_id": "STACKIT project ID to which the Load Balancer is associated.", - "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", - "listeners": "List of all listeners which will accept traffic. Limited to 20.", - "port": "Port number where we listen for traffic.", - "protocol": "Protocol is the highest network protocol we understand to load balance.", - "target_pool": "Reference target pool by target pool name.", - "name": "Load balancer name.", - "networks": "List of networks that listeners and targets reside in.", - "network_id": "Openstack network ID.", - "role": "The role defines how the load balancer is using the network.", - "options": "Defines any optional functionality you want to have enabled on your load balancer.", - "acl": "Load Balancer is accessible only from an IP address in this range.", - "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", - "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", - "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", - "private_address": "Transient private Load Balancer IP address. It can change any time.", - "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", - "healthy_threshold": "Healthy threshold of the health checking.", - "interval": "Interval duration of health checking in seconds.", - "interval_jitter": "Interval duration threshold of the health checking in seconds.", - "timeout": "Active health checking timeout duration in seconds.", - "unhealthy_threshold": "Unhealthy threshold of the health checking.", - "target_pools.name": "Target pool name.", - "target_port": "Identical port number where each target listens for traffic.", - "targets": "List of all targets which will be used in the pool. Limited to 250.", - "targets.display_name": "Target display name", - "ip": "Target IP", + "main": "Load Balancer resource schema.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"`name`\".", + "project_id": "STACKIT project ID to which the Load Balancer is associated.", + "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", + "listeners": "List of all listeners which will accept traffic. Limited to 20.", + "port": "Port number where we listen for traffic.", + "protocol": "Protocol is the highest network protocol we understand to load balance.", + "target_pool": "Reference target pool by target pool name.", + "name": "Load balancer name.", + "networks": "List of networks that listeners and targets reside in.", + "network_id": "Openstack network ID.", + "role": "The role defines how the load balancer is using the network.", + "options": "Defines any optional functionality you want to have enabled on your load balancer.", + "acl": "Load Balancer is accessible only from an IP address in this range.", + "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", + "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", + "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", + "server_name_indicators": "A list of domain names to match in order to pass TLS traffic to the target pool in the current listener", + "server_name_indicators.name": "A domain name to match in order to pass TLS traffic to the target pool in the current listener", + "private_address": "Transient private Load Balancer IP address. It can change any time.", + "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", + "healthy_threshold": "Healthy threshold of the health checking.", + "interval": "Interval duration of health checking in seconds.", + "interval_jitter": "Interval duration threshold of the health checking in seconds.", + "timeout": "Active health checking timeout duration in seconds.", + "unhealthy_threshold": "Unhealthy threshold of the health checking.", + "target_pools.name": "Target pool name.", + "target_port": "Identical port number where each target listens for traffic.", + "targets": "List of all targets which will be used in the pool. Limited to 250.", + "targets.display_name": "Target display name", + "ip": "Target IP", } resp.Schema = schema.Schema{ @@ -147,6 +149,18 @@ func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRe Description: descriptions["protocol"], Computed: true, }, + "server_name_indicators": schema.ListNestedAttribute{ + Description: descriptions["server_name_indicators"], + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: descriptions["server_name_indicators.name"], + Optional: true, + }, + }, + }, + }, "target_pool": schema.StringAttribute{ Description: descriptions["target_pool"], Computed: true, diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource.go b/stackit/internal/services/loadbalancer/loadbalancer/resource.go index 42b2f87b..ef1e663a 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource.go @@ -55,10 +55,21 @@ type Model struct { // Struct corresponding to each Model.Listener type Listener struct { - DisplayName types.String `tfsdk:"display_name"` - Port types.Int64 `tfsdk:"port"` - Protocol types.String `tfsdk:"protocol"` - TargetPool types.String `tfsdk:"target_pool"` + DisplayName types.String `tfsdk:"display_name"` + Port types.Int64 `tfsdk:"port"` + Protocol types.String `tfsdk:"protocol"` + ServerNameIndicators types.List `tfsdk:"server_name_indicators"` + TargetPool types.String `tfsdk:"target_pool"` +} + +// Struct corresponding to Listener.ServerNameIndicators[i] +type ServerNameIndicator struct { + Name types.String `tfsdk:"name"` +} + +// Types corresponding to ServerNameIndicator +var serverNameIndicatorTypes = map[string]attr.Type{ + "name": basetypes.StringType{}, } // Struct corresponding to each Model.Network @@ -177,35 +188,37 @@ func (r *loadBalancerResource) Configure(ctx context.Context, req resource.Confi // Schema defines the schema for the resource. func (r *loadBalancerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { descriptions := map[string]string{ - "main": "Load Balancer resource schema. Must have a `region` specified in the provider configuration.", - "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"`name`\".", - "project_id": "STACKIT project ID to which the Load Balancer is associated.", - "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", - "listeners": "List of all listeners which will accept traffic. Limited to 20.", - "port": "Port number where we listen for traffic.", - "protocol": "Protocol is the highest network protocol we understand to load balance.", - "target_pool": "Reference target pool by target pool name.", - "name": "Load balancer name.", - "networks": "List of networks that listeners and targets reside in.", - "network_id": "Openstack network ID.", - "role": "The role defines how the load balancer is using the network.", - "options": "Defines any optional functionality you want to have enabled on your load balancer.", - "acl": "Load Balancer is accessible only from an IP address in this range.", - "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", - "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", - "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", - "private_address": "Transient private Load Balancer IP address. It can change any time.", - "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", - "healthy_threshold": "Healthy threshold of the health checking.", - "interval": "Interval duration of health checking in seconds.", - "interval_jitter": "Interval duration threshold of the health checking in seconds.", - "timeout": "Active health checking timeout duration in seconds.", - "unhealthy_threshold": "Unhealthy threshold of the health checking.", - "target_pools.name": "Target pool name.", - "target_port": "Identical port number where each target listens for traffic.", - "targets": "List of all targets which will be used in the pool. Limited to 250.", - "targets.display_name": "Target display name", - "ip": "Target IP", + "main": "Load Balancer resource schema.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`\",\"`name`\".", + "project_id": "STACKIT project ID to which the Load Balancer is associated.", + "external_address": "External Load Balancer IP address where this Load Balancer is exposed.", + "listeners": "List of all listeners which will accept traffic. Limited to 20.", + "port": "Port number where we listen for traffic.", + "protocol": "Protocol is the highest network protocol we understand to load balance.", + "target_pool": "Reference target pool by target pool name.", + "name": "Load balancer name.", + "networks": "List of networks that listeners and targets reside in.", + "network_id": "Openstack network ID.", + "role": "The role defines how the load balancer is using the network.", + "options": "Defines any optional functionality you want to have enabled on your load balancer.", + "acl": "Load Balancer is accessible only from an IP address in this range.", + "private_network_only": "If true, Load Balancer is accessible only via a private network IP address.", + "session_persistence": "Here you can setup various session persistence options, so far only \"`use_source_ip_address`\" is supported.", + "use_source_ip_address": "If true then all connections from one source IP address are redirected to the same target. This setting changes the load balancing algorithm to Maglev.", + "server_name_indicators": "A list of domain names to match in order to pass TLS traffic to the target pool in the current listener", + "server_name_indicators.name": "A domain name to match in order to pass TLS traffic to the target pool in the current listener", + "private_address": "Transient private Load Balancer IP address. It can change any time.", + "target_pools": "List of all target pools which will be used in the Load Balancer. Limited to 20.", + "healthy_threshold": "Healthy threshold of the health checking.", + "interval": "Interval duration of health checking in seconds.", + "interval_jitter": "Interval duration threshold of the health checking in seconds.", + "timeout": "Active health checking timeout duration in seconds.", + "unhealthy_threshold": "Unhealthy threshold of the health checking.", + "target_pools.name": "Target pool name.", + "target_port": "Identical port number where each target listens for traffic.", + "targets": "List of all targets which will be used in the pool. Limited to 250.", + "targets.display_name": "Target display name", + "ip": "Target IP", } resp.Schema = schema.Schema{ @@ -305,7 +318,19 @@ The example below uses OpenStack to create the network, router, a public IP addr stringplanmodifier.UseStateForUnknown(), }, Validators: []validator.String{ - stringvalidator.OneOf("PROTOCOL_UNSPECIFIED", "PROTOCOL_TCP", "PROTOCOL_UDP", "PROTOCOL_TCP_PROXY"), + stringvalidator.OneOf("PROTOCOL_UNSPECIFIED", "PROTOCOL_TCP", "PROTOCOL_UDP", "PROTOCOL_TCP_PROXY", "PROTOCOL_TLS_PASSTHROUGH"), + }, + }, + "server_name_indicators": schema.ListNestedAttribute{ + Description: descriptions["server_name_indicators"], + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: descriptions["server_name_indicators.name"], + Optional: true, + }, + }, }, }, "target_pool": schema.StringAttribute{ @@ -709,7 +734,10 @@ func toCreatePayload(ctx context.Context, model *Model) (*loadbalancer.CreateLoa return nil, fmt.Errorf("nil model") } - listeners := toListenersPayload(model) + listeners, err := toListenersPayload(ctx, model) + if err != nil { + return nil, fmt.Errorf("converting listeners: %w", err) + } networks := toNetworksPayload(model) options, err := toOptionsPayload(ctx, model) if err != nil { @@ -730,22 +758,46 @@ func toCreatePayload(ctx context.Context, model *Model) (*loadbalancer.CreateLoa }, nil } -func toListenersPayload(model *Model) *[]loadbalancer.Listener { +func toListenersPayload(ctx context.Context, model *Model) (*[]loadbalancer.Listener, error) { if model.Listeners == nil { - return nil + return nil, nil } listeners := []loadbalancer.Listener{} - for _, listener := range model.Listeners { + for i := range model.Listeners { + listener := model.Listeners[i] + serverNameIndicators, err := toServerNameIndicatorsPayload(ctx, &listener) + if err != nil { + return nil, fmt.Errorf("converting index %d: converting ServerNameIndicator: %w", i, err) + } listeners = append(listeners, loadbalancer.Listener{ - DisplayName: conversion.StringValueToPointer(listener.DisplayName), - Port: conversion.Int64ValueToPointer(listener.Port), - Protocol: conversion.StringValueToPointer(listener.Protocol), - TargetPool: conversion.StringValueToPointer(listener.TargetPool), + DisplayName: conversion.StringValueToPointer(listener.DisplayName), + Port: conversion.Int64ValueToPointer(listener.Port), + Protocol: conversion.StringValueToPointer(listener.Protocol), + ServerNameIndicators: serverNameIndicators, + TargetPool: conversion.StringValueToPointer(listener.TargetPool), }) } - return &listeners + return &listeners, nil +} + +func toServerNameIndicatorsPayload(ctx context.Context, listener *Listener) (*[]loadbalancer.ServerNameIndicator, error) { + serverNameIndicators := []ServerNameIndicator{} + diags := listener.ServerNameIndicators.ElementsAs(ctx, &serverNameIndicators, false) + if diags.HasError() { + return nil, core.DiagsToError(diags) + } + + payload := []loadbalancer.ServerNameIndicator{} + for i := range serverNameIndicators { + indicator := serverNameIndicators[i] + payload = append(payload, loadbalancer.ServerNameIndicator{ + Name: conversion.StringValueToPointer(indicator.Name), + }) + } + + return &payload, nil } func toNetworksPayload(model *Model) *[]loadbalancer.Network { @@ -930,9 +982,12 @@ func mapFields(ctx context.Context, lb *loadbalancer.LoadBalancer, m *Model) err m.ExternalAddress = types.StringPointerValue(lb.ExternalAddress) m.PrivateAddress = types.StringPointerValue(lb.PrivateAddress) - mapListeners(lb, m) + err := mapListeners(lb, m) + if err != nil { + return fmt.Errorf("mapping listeners: %w", err) + } mapNetworks(lb, m) - err := mapOptions(ctx, lb, m) + err = mapOptions(ctx, lb, m) if err != nil { return fmt.Errorf("mapping options: %w", err) } @@ -944,21 +999,57 @@ func mapFields(ctx context.Context, lb *loadbalancer.LoadBalancer, m *Model) err return nil } -func mapListeners(lb *loadbalancer.LoadBalancer, m *Model) { +func mapServerNameIndicators(serverNameIndicatorsResp *[]loadbalancer.ServerNameIndicator, l *Listener) error { + if serverNameIndicatorsResp == nil || *serverNameIndicatorsResp == nil { + l.ServerNameIndicators = types.ListNull(types.ObjectType{AttrTypes: serverNameIndicatorTypes}) + return nil + } + + serverNameIndicators := []attr.Value{} + for i, serverNameIndicatorResp := range *serverNameIndicatorsResp { + serverNameIndicator := map[string]attr.Value{ + "name": types.StringPointerValue(serverNameIndicatorResp.Name), + } + serverNameIndicatorTF, diags := basetypes.NewObjectValue(serverNameIndicatorTypes, serverNameIndicator) + if diags.HasError() { + return fmt.Errorf("mapping index %d: %w", i, core.DiagsToError(diags)) + } + serverNameIndicators = append(serverNameIndicators, serverNameIndicatorTF) + } + + serverNameIndicatorsTF, diags := basetypes.NewListValue( + types.ObjectType{AttrTypes: serverNameIndicatorTypes}, + serverNameIndicators, + ) + if diags.HasError() { + return core.DiagsToError(diags) + } + + l.ServerNameIndicators = serverNameIndicatorsTF + return nil +} + +func mapListeners(lb *loadbalancer.LoadBalancer, m *Model) error { if lb.Listeners == nil { - return + return nil } var listeners []Listener - for _, listener := range *lb.Listeners { - listeners = append(listeners, Listener{ + for i, listener := range *lb.Listeners { + listenerTF := Listener{ DisplayName: types.StringPointerValue(listener.DisplayName), Port: types.Int64PointerValue(listener.Port), Protocol: types.StringPointerValue(listener.Protocol), TargetPool: types.StringPointerValue(listener.TargetPool), - }) + } + err := mapServerNameIndicators(listener.ServerNameIndicators, &listenerTF) + if err != nil { + return fmt.Errorf("mapping index %d, field serverNameIndicators: %w", i, err) + } + listeners = append(listeners, listenerTF) } m.Listeners = listeners + return nil } func mapNetworks(lb *loadbalancer.LoadBalancer, m *Model) { diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go index 91f73ed5..fd5cbf2f 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go @@ -45,7 +45,18 @@ func TestToCreatePayload(t *testing.T) { DisplayName: types.StringValue("display_name"), Port: types.Int64Value(80), Protocol: types.StringValue("protocol"), - TargetPool: types.StringValue("target_pool"), + ServerNameIndicators: types.ListValueMust( + types.ObjectType{AttrTypes: serverNameIndicatorTypes}, + []attr.Value{ + types.ObjectValueMust( + serverNameIndicatorTypes, + map[string]attr.Value{ + "name": types.StringValue("domain.com"), + }, + ), + }, + ), + TargetPool: types.StringValue("target_pool"), }, }, Name: types.StringValue("name"), @@ -104,7 +115,12 @@ func TestToCreatePayload(t *testing.T) { DisplayName: utils.Ptr("display_name"), Port: utils.Ptr(int64(80)), Protocol: utils.Ptr("protocol"), - TargetPool: utils.Ptr("target_pool"), + ServerNameIndicators: &[]loadbalancer.ServerNameIndicator{ + { + Name: utils.Ptr("domain.com"), + }, + }, + TargetPool: utils.Ptr("target_pool"), }, }), Name: utils.Ptr("name"), @@ -303,7 +319,12 @@ func TestMapFields(t *testing.T) { DisplayName: utils.Ptr("display_name"), Port: utils.Ptr(int64(80)), Protocol: utils.Ptr("protocol"), - TargetPool: utils.Ptr("target_pool"), + ServerNameIndicators: &[]loadbalancer.ServerNameIndicator{ + { + Name: utils.Ptr("domain.com"), + }, + }, + TargetPool: utils.Ptr("target_pool"), }, }), Name: utils.Ptr("name"), @@ -356,7 +377,18 @@ func TestMapFields(t *testing.T) { DisplayName: types.StringValue("display_name"), Port: types.Int64Value(80), Protocol: types.StringValue("protocol"), - TargetPool: types.StringValue("target_pool"), + ServerNameIndicators: types.ListValueMust( + types.ObjectType{AttrTypes: serverNameIndicatorTypes}, + []attr.Value{ + types.ObjectValueMust( + serverNameIndicatorTypes, + map[string]attr.Value{ + "name": types.StringValue("domain.com"), + }, + ), + }, + ), + TargetPool: types.StringValue("target_pool"), }, }, Networks: []Network{ diff --git a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go index 59509de9..df79b33a 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go @@ -16,26 +16,24 @@ import ( // Instance resource data var loadBalancerResource = map[string]string{ - "project_id": testutil.ProjectId, - "name": fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)), - "target_pool_name": "example-target-pool", - "target_port": "5432", - "target_port_updated": "5431", - "target_display_name": "example-target", - "healthy_threshold": "3", - "interval": "10s", - "interval_jitter": "5s", - "timeout": "10s", - "unhealthy_threshold": "3", - "use_source_ip_address": "true", - "listener_display_name": "example-listener", - "listener_port": "5432", - "listener_protocol": "PROTOCOL_TCP", - "network_role": "ROLE_LISTENERS_AND_TARGETS", - "private_network_only": "true", - "credential_display_name": fmt.Sprintf("tf-acc-cred%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)), - "credential_username": "username", - "credential_password": "password", + "project_id": testutil.ProjectId, + "name": fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum)), + "target_pool_name": "example-target-pool", + "target_port": "5432", + "target_port_updated": "5431", + "target_display_name": "example-target", + "healthy_threshold": "3", + "interval": "10s", + "interval_jitter": "5s", + "timeout": "10s", + "unhealthy_threshold": "3", + "use_source_ip_address": "true", + "listener_display_name": "example-listener", + "listener_port": "5432", + "listener_protocol": "PROTOCOL_TLS_PASSTHROUGH", + "serverNameIndicator": "domain.com", + "network_role": "ROLE_LISTENERS_AND_TARGETS", + "private_network_only": "true", } func configResources(targetPort string) string { @@ -74,6 +72,11 @@ func configResources(targetPort string) string { display_name = "%s" port = %s protocol = "%s" + server_name_indictors = [ + { + "name": "%s" + } + ] target_pool = "%s" } ] @@ -115,6 +118,7 @@ func configResources(targetPort string) string { loadBalancerResource["listener_display_name"], loadBalancerResource["listener_port"], loadBalancerResource["listener_protocol"], + loadBalancerResource["serverNameIndicator"], loadBalancerResource["target_pool_name"], loadBalancerResource["network_role"], loadBalancerResource["private_network_only"], @@ -230,6 +234,7 @@ func TestAccLoadBalancerResource(t *testing.T) { resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.display_name", loadBalancerResource["listener_display_name"]), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.port", loadBalancerResource["listener_port"]), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.protocol", loadBalancerResource["listener_protocol"]), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.serverNameIndicators.0.name", loadBalancerResource["serverNameIndicator"]), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.target_pool", loadBalancerResource["target_pool_name"]), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "networks.0.role", loadBalancerResource["network_role"]), @@ -284,6 +289,7 @@ func TestAccLoadBalancerResource(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.display_name", loadBalancerResource["listener_display_name"]), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.port", loadBalancerResource["listener_port"]), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.protocol", loadBalancerResource["listener_protocol"]), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.serverNameIndicators.0.name", loadBalancerResource["serverNameIndicator"]), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.target_pool", loadBalancerResource["target_pool_name"]), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "networks.0.role", loadBalancerResource["network_role"]),