zeroshade commented on code in PR #8:
URL: https://github.com/apache/iceberg-terraform/pull/8#discussion_r2765937860


##########
internal/provider/resource_namespace.go:
##########
@@ -0,0 +1,410 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 provider
+
+import (
+       "context"
+       "errors"
+       "strings"
+
+       "github.com/apache/iceberg-go"
+       "github.com/apache/iceberg-go/catalog"
+       "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/resource/schema/listplanmodifier"
+       
"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"
+)
+
+var (
+       _ resource.Resource = &icebergNamespaceResource{}
+)
+
+func NewNamespaceResource() resource.Resource {
+       return &icebergNamespaceResource{}
+}
+
+type icebergNamespaceResourceModel struct {
+       ID             types.String `tfsdk:"id"`
+       Name           types.List   `tfsdk:"name"`
+       Properties     types.Map    `tfsdk:"properties"`
+       FullProperties types.Map    `tfsdk:"full_properties"`
+}
+
+type icebergNamespaceResource struct {
+       catalog  catalog.Catalog
+       provider *icebergProvider
+}
+
+func (r *icebergNamespaceResource) Metadata(_ context.Context, req 
resource.MetadataRequest, resp *resource.MetadataResponse) {
+       resp.TypeName = req.ProviderTypeName + "_namespace"
+}
+
+func (r *icebergNamespaceResource) Schema(_ context.Context, _ 
resource.SchemaRequest, resp *resource.SchemaResponse) {
+       resp.Schema = schema.Schema{
+               Description: "A resource for managing Iceberg namespaces.",
+               Attributes: map[string]schema.Attribute{
+                       "id": schema.StringAttribute{
+                               Computed: true,
+                               PlanModifiers: []planmodifier.String{
+                                       stringplanmodifier.UseStateForUnknown(),
+                               },
+                       },
+                       "name": schema.ListAttribute{
+                               Description: "The name of the namespace.",
+                               Required:    true,
+                               ElementType: types.StringType,
+                               PlanModifiers: []planmodifier.List{
+                                       listplanmodifier.RequiresReplace(),
+                               },
+                       },
+                       "properties": schema.MapAttribute{
+                               Description: "User-defined properties for the 
namespace. Only properties listed in Terraform will be changed. All others on 
the server will stay the same",
+                               Optional:    true,
+                               ElementType: types.StringType,
+                       },
+                       "full_properties": schema.MapAttribute{
+                               Description: "Full properties returned by the 
server for the namespace. This includes properties set by the user and 
properties set by the server.",
+                               Computed:    true,
+                               ElementType: types.StringType,
+                       },
+               },
+       }
+}
+
+func (r *icebergNamespaceResource) Configure(ctx context.Context, req 
resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+       if req.ProviderData == nil {
+               return
+       }
+
+       provider, ok := req.ProviderData.(*icebergProvider)
+       if !ok {
+               resp.Diagnostics.AddError(
+                       "Unexpected Resource Configure Type",
+                       "Expected *icebergProvider, got: %T. Please report this 
issue to the provider developers.",
+               )
+               return
+       }
+
+       r.provider = provider
+}
+
+func (r *icebergNamespaceResource) ConfigureCatalog(ctx context.Context, diags 
*diag.Diagnostics) {
+       if r.catalog != nil {
+               return
+       }
+
+       if r.provider == nil {
+               diags.AddError(
+                       "Provider not configured",
+                       "The provider hasn't been configured before this 
operation",
+               )
+               return
+       }
+
+       catalog, err := r.provider.NewCatalog(ctx)
+       if err != nil {
+               diags.AddError(
+                       "Failed to create catalog",
+                       "Failed to create catalog: "+err.Error(),
+               )
+               return
+       }
+       r.catalog = catalog
+}
+
+func (r *icebergNamespaceResource) Create(ctx context.Context, req 
resource.CreateRequest, resp *resource.CreateResponse) {
+       r.ConfigureCatalog(ctx, &resp.Diagnostics)
+       if resp.Diagnostics.HasError() {
+               return
+       }
+
+       var data icebergNamespaceResourceModel
+
+       diags := req.Plan.Get(ctx, &data)
+       resp.Diagnostics.Append(diags...)
+
+       if resp.Diagnostics.HasError() {
+               return
+       }
+
+       var namespaceName []string
+       diags = data.Name.ElementsAs(ctx, &namespaceName, false)
+       resp.Diagnostics.Append(diags...)
+       if resp.Diagnostics.HasError() {
+               return
+       }
+
+       namespaceIdent := catalog.ToIdentifier(namespaceName...)
+
+       properties := make(map[string]string)
+       if !data.Properties.IsNull() {
+               diags = data.Properties.ElementsAs(ctx, &properties, false)
+               resp.Diagnostics.Append(diags...)
+               if resp.Diagnostics.HasError() {
+                       return
+               }
+       }
+
+       err := r.catalog.CreateNamespace(ctx, namespaceIdent, properties)
+       if err != nil {
+               resp.Diagnostics.AddError("failed to create namespace", 
err.Error())
+               return
+       }
+
+       data.ID = types.StringValue(strings.Join(namespaceIdent, "."))
+
+       nsProps, err := r.catalog.LoadNamespaceProperties(ctx, namespaceIdent)
+       if err != nil {
+               resp.Diagnostics.AddError("failed to read namespace 
properties", err.Error())
+               return
+       }
+
+       // Update FullProperties with everything from the server
+       loadedFullProperties, diags := types.MapValueFrom(ctx, 
types.StringType, nsProps)
+       resp.Diagnostics.Append(diags...)
+       if resp.Diagnostics.HasError() {
+               return
+       }
+       data.FullProperties = loadedFullProperties
+
+       // Update Properties to match what we sent/expected, but values 
confirmed from server
+       // We only keep keys that were in the original plan (User managed)
+       managedProps := make(map[string]string)
+       for k := range properties {
+               if v, ok := nsProps[k]; ok {
+                       managedProps[k] = v
+               }
+       }
+       // If the user didn't set any properties, Properties should be null or 
empty based on input.
+       // However, if we sent it, we expect it back.
+       if !data.Properties.IsNull() {
+               data.Properties, diags = types.MapValueFrom(ctx, 
types.StringType, managedProps)
+               resp.Diagnostics.Append(diags...)
+       }
+
+       diags = resp.State.Set(ctx, &data)
+       resp.Diagnostics.Append(diags...)
+}
+
+func (r *icebergNamespaceResource) Read(ctx context.Context, req 
resource.ReadRequest, resp *resource.ReadResponse) {
+       r.ConfigureCatalog(ctx, &resp.Diagnostics)
+       if resp.Diagnostics.HasError() {
+               return
+       }
+
+       var data icebergNamespaceResourceModel
+
+       tflog.Info(ctx, "Reading namespace resource")
+       diags := req.State.Get(ctx, &data)
+       resp.Diagnostics.Append(diags...)
+
+       if resp.Diagnostics.HasError() {
+               return
+       }
+
+       var namespaceName []string
+       diags = data.Name.ElementsAs(ctx, &namespaceName, false)
+       resp.Diagnostics.Append(diags...)
+       if resp.Diagnostics.HasError() {
+               return
+       }
+
+       namespaceIdent := catalog.ToIdentifier(namespaceName...)
+
+       nsProps, err := r.catalog.LoadNamespaceProperties(ctx, namespaceIdent)
+       if err != nil {
+               if errors.Is(err, catalog.ErrNoSuchNamespace) {
+                       resp.State.RemoveResource(ctx)
+                       return
+               }
+               resp.Diagnostics.AddError("failed to load namespace", 
err.Error())
+               return
+       }
+
+       // FullProperties gets everything
+       fullProperties, diags := types.MapValueFrom(ctx, types.StringType, 
nsProps)
+       resp.Diagnostics.Append(diags...)
+       if resp.Diagnostics.HasError() {
+               return
+       }
+       data.FullProperties = fullProperties
+
+       // Properties only updates keys that are already tracked in the state
+       if !data.Properties.IsNull() {
+               stateProperties := make(map[string]string)
+               diags = data.Properties.ElementsAs(ctx, &stateProperties, false)
+               resp.Diagnostics.Append(diags...)
+               if resp.Diagnostics.HasError() {
+                       return
+               }
+
+               managedProps := make(map[string]string)
+               for k := range stateProperties {
+                       if v, ok := nsProps[k]; ok {
+                               managedProps[k] = v
+                       }
+                       // If key is missing in nsProps, it was removed from 
server, so we drop it from managedProps
+                       // which effectively sets it to null/removed in the new 
state, matching reality.
+               }
+               data.Properties, diags = types.MapValueFrom(ctx, 
types.StringType, managedProps)
+               resp.Diagnostics.Append(diags...)
+       }
+
+       diags = resp.State.Set(ctx, &data)
+       resp.Diagnostics.Append(diags...)
+}
+
+func (r *icebergNamespaceResource) Update(ctx context.Context, req 
resource.UpdateRequest, resp *resource.UpdateResponse) {
+       r.ConfigureCatalog(ctx, &resp.Diagnostics)
+       if resp.Diagnostics.HasError() {
+               return
+       }
+
+       var plan, state icebergNamespaceResourceModel
+
+       diags := req.Plan.Get(ctx, &plan)
+       resp.Diagnostics.Append(diags...)
+
+       diags = req.State.Get(ctx, &state)
+       resp.Diagnostics.Append(diags...)

Review Comment:
   that's about how I understood it, so if the configuration is set up properly 
then `Update` would only ever need to handle cases that are valid for update, 
and for situations that require recreating the configuration should handle it. 
gotcha! thanks!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to