From 4c462db11a8b9a971f91f17bdb4baebb78182a5e Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Mon, 8 Apr 2024 07:29:08 -0300 Subject: [PATCH] OCM-6031 | feat: allow to edit component routes of ingress --- docs/resources/default_ingress.md | 9 +++ provider/defaultingress/classic/resource.go | 47 ++++++++++++++- provider/defaultingress/classic/state.go | 17 +++--- provider/defaultingress/component_routes.go | 48 +++++++++++++++ subsystem/default_ingress_resource_test.go | 65 +++++++++++++++++++++ 5 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 provider/defaultingress/component_routes.go diff --git a/docs/resources/default_ingress.md b/docs/resources/default_ingress.md index 29e75de2..3a9cad50 100644 --- a/docs/resources/default_ingress.md +++ b/docs/resources/default_ingress.md @@ -23,9 +23,18 @@ Edit a cluster ingress (load balancer) - `cluster_routes_hostname` (String) Components route hostname for oauth, console, download. - `cluster_routes_tls_secret_ref` (String) Components route TLS secret reference for oauth, console, download. +- `component_routes` (Map of Object) Component route parameters for oauth, console, downloads. (see [below for nested schema](#nestedatt--component_routes)) - `excluded_namespaces` (List of String) Excluded namespaces for ingress. Format should be a comma-separated list 'value1, value2...'. If no values are specified, all namespaces will be exposed. - `id` (String) Unique identifier of the ingress. - `load_balancer_type` (String) Type of Load Balancer. Options are classic,nlb. - `route_namespace_ownership_policy` (String) Namespace Ownership Policy for ingress. Options are Strict,InterNamespaceAllowed. Default is 'Strict'. - `route_selectors` (Map of String) Route Selectors for ingress. Format should be a comma-separated list of 'key=value'. If no label is specified, all routes will be exposed on both routers.For legacy ingress support these are inclusion labels, otherwise they are treated as exclusion label. - `route_wildcard_policy` (String) Wildcard Policy for ingress. Options are WildcardsDisallowed,WildcardsAllowed. Default is 'WildcardsDisallowed'. + + +### Nested Schema for `component_routes` + +Optional: + +- `hostname` (String) +- `tls_secret_ref` (String) diff --git a/provider/defaultingress/classic/resource.go b/provider/defaultingress/classic/resource.go index e259a7fd..b00079d8 100644 --- a/provider/defaultingress/classic/resource.go +++ b/provider/defaultingress/classic/resource.go @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/resource/schema" @@ -16,12 +18,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" sdk "github.com/openshift-online/ocm-sdk-go" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" "github.com/terraform-redhat/terraform-provider-rhcs/provider/common" "github.com/terraform-redhat/terraform-provider-rhcs/provider/common/attrvalidators" + "github.com/terraform-redhat/terraform-provider-rhcs/provider/defaultingress" ) var validWildcardPolicies = []string{string(cmv1.WildcardPolicyWildcardsDisallowed), @@ -120,6 +124,13 @@ func (r *DefaultIngressResource) Schema(ctx context.Context, req resource.Schema Optional: true, Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, + "component_routes": schema.MapAttribute{ + Description: "Component route parameters for oauth, console, downloads.", + ElementType: basetypes.ObjectType{ + AttrTypes: defaultingress.ComponentRouteAttributeTypes, + }, + Optional: true, + }, }, } return @@ -163,7 +174,7 @@ func (r *DefaultIngressResource) Create(ctx context.Context, req resource.Create ) return } - err = r.updateIngress(ctx, nil, plan, plan.Cluster.ValueString(), r.collection) + err = r.updateIngress(ctx, nil, plan, plan.Cluster.ValueString(), r.collection, resp.Diagnostics) if err != nil { resp.Diagnostics.AddError( "Failed building cluster default ingress", @@ -230,7 +241,7 @@ func (r *DefaultIngressResource) Update(ctx context.Context, req resource.Update return } - err := r.updateIngress(ctx, state, plan, plan.Cluster.ValueString(), r.collection) + err := r.updateIngress(ctx, state, plan, plan.Cluster.ValueString(), r.collection, resp.Diagnostics) if err != nil { diags.AddError( "Failed to update default ingress", @@ -365,13 +376,31 @@ func (r *DefaultIngressResource) populateState(ingress *cmv1.Ingress, state *Def } else { state.ClusterRoutesTlsSecretRef = types.StringNull() } + componentRoutes, ok := ingress.GetComponentRoutes() + if ok { + elements := map[string]attr.Value{} + for k, v := range componentRoutes { + elements[k] = defaultingress.FlattenComponentRoute(v.Hostname(), v.TlsSecretRef()) + } + mapValue, diags := types.MapValue(types.ObjectType{ + AttrTypes: defaultingress.ComponentRouteAttributeTypes, + }, elements) + if diags != nil && diags.HasError() { + return fmt.Errorf("failed to convert to MapType %v", diags.Errors()[0].Detail()) + } + state.ComponentRoutes = mapValue + } else { + state.ComponentRoutes = types.MapNull(types.ObjectType{ + AttrTypes: defaultingress.ComponentRouteAttributeTypes, + }) + } state.LoadBalancerType = types.StringValue(string(ingress.LoadBalancerType())) return nil } func (r *DefaultIngressResource) updateIngress(ctx context.Context, state, plan *DefaultIngress, - clusterId string, clusterCollection *cmv1.ClustersClient) error { + clusterId string, clusterCollection *cmv1.ClustersClient, diags diag.Diagnostics) error { if state == nil { state = &DefaultIngress{Cluster: plan.Cluster} @@ -411,6 +440,18 @@ func (r *DefaultIngressResource) updateIngress(ctx context.Context, state, plan ingressBuilder.ClusterRoutesTlsSecretRef(value) } + if !reflect.DeepEqual(state.ComponentRoutes, plan.ComponentRoutes) { + componentRoutes := map[string]*cmv1.ComponentRouteBuilder{} + for k, v := range plan.ComponentRoutes.Elements() { + componentRouteBuilder := cmv1.NewComponentRoute() + hostname, tlsSecretRef := defaultingress.ExpandComponentRoute(ctx, v.(types.Object), diags) + componentRouteBuilder.Hostname(hostname) + componentRouteBuilder.TlsSecretRef(tlsSecretRef) + componentRoutes[k] = componentRouteBuilder + } + ingressBuilder.ComponentRoutes(componentRoutes) + } + ingress, err := ingressBuilder.Build() if err != nil { return err diff --git a/provider/defaultingress/classic/state.go b/provider/defaultingress/classic/state.go index 8059151b..a8d8c400 100644 --- a/provider/defaultingress/classic/state.go +++ b/provider/defaultingress/classic/state.go @@ -3,13 +3,16 @@ package classic import "github.com/hashicorp/terraform-plugin-framework/types" type DefaultIngress struct { - Cluster types.String `tfsdk:"cluster"` - RouteSelectors types.Map `tfsdk:"route_selectors"` - ExcludedNamespaces types.List `tfsdk:"excluded_namespaces"` - WildcardPolicy types.String `tfsdk:"route_wildcard_policy"` - NamespaceOwnershipPolicy types.String `tfsdk:"route_namespace_ownership_policy"` - Id types.String `tfsdk:"id"` + Cluster types.String `tfsdk:"cluster"` + RouteSelectors types.Map `tfsdk:"route_selectors"` + ExcludedNamespaces types.List `tfsdk:"excluded_namespaces"` + WildcardPolicy types.String `tfsdk:"route_wildcard_policy"` + NamespaceOwnershipPolicy types.String `tfsdk:"route_namespace_ownership_policy"` + Id types.String `tfsdk:"id"` + LoadBalancerType types.String `tfsdk:"load_balancer_type"` + ComponentRoutes types.Map `tfsdk:"component_routes"` + + // Soon to be deprecated ClusterRoutesHostname types.String `tfsdk:"cluster_routes_hostname"` ClusterRoutesTlsSecretRef types.String `tfsdk:"cluster_routes_tls_secret_ref"` - LoadBalancerType types.String `tfsdk:"load_balancer_type"` } diff --git a/provider/defaultingress/component_routes.go b/provider/defaultingress/component_routes.go new file mode 100644 index 00000000..d3588122 --- /dev/null +++ b/provider/defaultingress/component_routes.go @@ -0,0 +1,48 @@ +package defaultingress + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +type ComponentRoute struct { + Hostname types.String `tfsdk:"hostname"` + TlsSecretRef types.String `tfsdk:"tls_secret_ref"` +} + +var ComponentRouteAttributeTypes = map[string]attr.Type{ + "hostname": types.StringType, + "tls_secret_ref": types.StringType, +} + +func FlattenComponentRoute(hostname, tlsSecretRef string) types.Object { + if hostname == "" && tlsSecretRef == "" { + return types.ObjectNull(ComponentRouteAttributeTypes) + } + + attrs := map[string]attr.Value{ + "hostname": types.StringValue(hostname), + "tls_secret_ref": types.StringValue(tlsSecretRef), + } + + return types.ObjectValueMust(ComponentRouteAttributeTypes, attrs) +} + +func ExpandComponentRoute(ctx context.Context, + object types.Object, diags diag.Diagnostics) (string, string) { + if object.IsNull() { + return "", "" + } + + var componentRoute ComponentRoute + diags.Append(object.As(ctx, &componentRoute, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return "", "" + } + + return componentRoute.Hostname.ValueString(), componentRoute.TlsSecretRef.ValueString() +} diff --git a/subsystem/default_ingress_resource_test.go b/subsystem/default_ingress_resource_test.go index 6b74fd0d..da2cec6b 100644 --- a/subsystem/default_ingress_resource_test.go +++ b/subsystem/default_ingress_resource_test.go @@ -506,6 +506,71 @@ var _ = Describe("default ingress", func() { Expect(terraform.Apply()).To(Equal(1)) }) + It("Create cluster with default ingress - component_routes set to actual value", func() { + // Prepare the server: + server.AppendHandlers( + CombineHandlers( + VerifyRequest(http.MethodGet, "/api/clusters_mgmt/v1/clusters/123"), + RespondWithJSON(http.StatusOK, clusterReady), + ), + CombineHandlers( + VerifyRequest(http.MethodGet, "/api/clusters_mgmt/v1/clusters/123/ingresses"), + RespondWithJSON(http.StatusOK, defaultDay1Template), + ), + + CombineHandlers( + VerifyRequest(http.MethodPatch, "/api/clusters_mgmt/v1/clusters/123/ingresses/d6z2"), + RespondWithJSON(http.StatusOK, ` + { + "kind": "Ingress", + "href": "/api/clusters_mgmt/v1/clusters/123/ingresses/d6z2", + "id": "d6z2", + "listening": "external", + "default": true, + "dns_name": "redhat.com", + "load_balancer_type": "classic", + "route_wildcard_policy": "WildcardsDisallowed", + "route_namespace_ownership_policy": "Strict", + "component_routes": { + "console": { + "hostname": "console-host", + "tls_secret_ref": "console-secret" + }, + "downloads": { + "hostname": "downloads-host", + "tls_secret_ref": "downloads-secret" + }, + "oauth": { + "hostname": "oauth-host-new", + "tls_secret_ref": "oauth-secret" + } + } + } + `), + ), + ) + // Run the apply command: + terraform.Source(` + resource "rhcs_default_ingress" "default_ingress" { + cluster = "123" + component_routes = { + "oauth" = { + "hostname" = "oauth-host-new" + "tls_secret_ref" = "oauth-secret" + } + "console" = { + "hostname" = "console-host" + "tls_secret_ref" = "console-secret" + } + "downloads" = { + "hostname" = "downloads-host" + "tls_secret_ref" = "downloads-secret" + } + } + }`) + Expect(terraform.Apply()).To(BeZero()) + }) + It("Create default ingress and delete it", func() { // Prepare the server: server.AppendHandlers(