Skip to content

Commit

Permalink
feat(cloud): implement Update function for Kubernetes cloud resource
Browse files Browse the repository at this point in the history
- Added `Update` function in `kubernetesCloudResource` to handle updating the Kubernetes cloud.
- Integrated the new method into the Terraform provider's resource lifecycle.
- Updated the schema and model to ensure proper state management during update operations.
  • Loading branch information
anvial committed Oct 2, 2024
1 parent d43d1a6 commit b106a60
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 43 deletions.
67 changes: 62 additions & 5 deletions internal/juju/kubernetes_clouds.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
package juju

import (
"strings"

"github.com/juju/errors"
"github.com/juju/juju/api/client/cloud"
k8s "github.com/juju/juju/caas/kubernetes"
k8scloud "github.com/juju/juju/caas/kubernetes/cloud"
"k8s.io/client-go/tools/clientcmd"
"strings"
)

type kubernetesCloudsClient struct {
Expand All @@ -36,6 +37,14 @@ type ReadKubernetesCloudOutput struct {
ParentCloudRegion string
}

type UpdateKubernetesCloudInput struct {
Name string
KubernetesContextName string
KubernetesConfig string
ParentCloudName string
ParentCloudRegion string
}

type DestroyKubernetesCloudInput struct {
Name string
}
Expand Down Expand Up @@ -94,7 +103,7 @@ func (c *kubernetesCloudsClient) CreateKubernetesCloud(input *CreateKubernetesCl
}

// ReadKubernetesCloud reads a Kubernetes cloud with juju cloud facade.
func (c *kubernetesCloudsClient) ReadKubernetesCloud(input *ReadKubernetesCloudInput) (*ReadKubernetesCloudOutput, error) {
func (c *kubernetesCloudsClient) ReadKubernetesCloud(input ReadKubernetesCloudInput) (*ReadKubernetesCloudOutput, error) {
conn, err := c.GetConnection(nil)
if err != nil {
return nil, err
Expand Down Expand Up @@ -123,8 +132,9 @@ func (c *kubernetesCloudsClient) ReadKubernetesCloud(input *ReadKubernetesCloudI
return nil, errors.NotFoundf("kubernetes cloud %q", input.Name)
}

// getParentCloudNameAndRegionFromHostCloudRegion returns the parent cloud name and region from the host cloud region.
// HostCloudRegion represents the k8s host cloud. The format is <cloudName>/<region>.
// getParentCloudNameAndRegionFromHostCloudRegion returns the parent cloud name
// and region from the host cloud region. HostCloudRegion represents the k8s
// host cloud. The format is <cloudName>/<region>.
func getParentCloudNameAndRegionFromHostCloudRegion(hostCloudRegion string) (string, string) {
parts := strings.Split(hostCloudRegion, "/")
if len(parts) != 2 {
Expand All @@ -133,8 +143,55 @@ func getParentCloudNameAndRegionFromHostCloudRegion(hostCloudRegion string) (str
return parts[0], parts[1]
}

// UpdateKubernetesCloud updates a Kubernetes cloud with juju cloud facade.
func (c *kubernetesCloudsClient) UpdateKubernetesCloud(input UpdateKubernetesCloudInput) error {
conn, err := c.GetConnection(nil)
if err != nil {
return err
}
defer func() { _ = conn.Close() }()

client := cloud.NewClient(conn)

conf, err := clientcmd.NewClientConfigFromBytes([]byte(input.KubernetesConfig))
if err != nil {
return errors.Annotate(err, "parsing kubernetes configuration data")
}

apiConf, err := conf.RawConfig()
if err != nil {
return errors.Annotate(err, "fetching kubernetes configuration")
}

var k8sContextName string
if input.KubernetesContextName == "" {
k8sContextName = apiConf.CurrentContext
} else {
k8sContextName = input.KubernetesContextName
}

newCloud, err := k8scloud.CloudFromKubeConfigContext(
k8sContextName,
&apiConf,
k8scloud.CloudParamaters{
Name: input.Name,
HostCloudRegion: k8s.K8sCloudOther,
},
)
if err != nil {
return errors.Trace(err)
}

err = client.UpdateCloud(newCloud)
if err != nil {
return errors.Annotate(err, "updating kubernetes cloud")
}

return nil
}

// RemoveKubernetesCloud removes a Kubernetes cloud with juju cloud facade.
func (c *kubernetesCloudsClient) RemoveKubernetesCloud(input *DestroyKubernetesCloudInput) error {
func (c *kubernetesCloudsClient) RemoveKubernetesCloud(input DestroyKubernetesCloudInput) error {
conn, err := c.GetConnection(nil)
if err != nil {
return err
Expand Down
20 changes: 10 additions & 10 deletions internal/provider/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ const (
LogDataSourceOffer = "datasource-offer"
LogDataSourceSecret = "datasource-secret"

LogResourceApplication = "resource-application"
LogResourceAccessModel = "resource-access-model"
LogResourceCredential = "resource-credential"
LogResourceApplication = "resource-application"
LogResourceAccessModel = "resource-access-model"
LogResourceCredential = "resource-credential"
LogResourceKubernetesCloud = "resource-kubernetes-cloud"
LogResourceMachine = "resource-machine"
LogResourceModel = "resource-model"
LogResourceOffer = "resource-offer"
LogResourceSSHKey = "resource-sshkey"
LogResourceUser = "resource-user"
LogResourceSecret = "resource-secret"
LogResourceAccessSecret = "resource-access-secret"
LogResourceMachine = "resource-machine"
LogResourceModel = "resource-model"
LogResourceOffer = "resource-offer"
LogResourceSSHKey = "resource-sshkey"
LogResourceUser = "resource-user"
LogResourceSecret = "resource-secret"
LogResourceAccessSecret = "resource-access-secret"

LogResourceJAASAccessModel = "resource-jaas-access-model"
LogResourceJAASAccessCloud = "resource-jaas-access-cloud"
Expand Down
84 changes: 56 additions & 28 deletions internal/provider/resource_kubernetes_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-log/tflog"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"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/juju/terraform-provider-juju/internal/juju"
)
Expand All @@ -36,14 +36,14 @@ type kubernetesCloudResource struct {

type kubernetesCloudResourceModel struct {
CloudName types.String `tfsdk:"name"`
KubernetesConfig types.String `tfsdk:"kubernetesconfig"`
ParentCloudName types.String `tfsdk:"parentcloudname"`
ParentCloudRegion types.String `tfsdk:"parentcloudregion"`
KubernetesConfig types.String `tfsdk:"kubernetes_config"`
ParentCloudName types.String `tfsdk:"parent_cloud_name"`
ParentCloudRegion types.String `tfsdk:"parent_cloud_region"`
// ID required by the testing framework
ID types.String `tfsdk:"id"`
}

func (o *kubernetesCloudResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
func (r *kubernetesCloudResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
Expand All @@ -57,20 +57,20 @@ func (o *kubernetesCloudResource) Configure(ctx context.Context, req resource.Co
)
return
}
o.client = client
r.client = client
// Create the local logging subsystem here, using the TF context when creating it.
o.subCtx = tflog.NewSubsystem(ctx, LogResourceKubernetesCloud)
r.subCtx = tflog.NewSubsystem(ctx, LogResourceKubernetesCloud)
}

func (o *kubernetesCloudResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
func (r *kubernetesCloudResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

func (o *kubernetesCloudResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
func (r *kubernetesCloudResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_kubernetes_cloud"
}

func (o *kubernetesCloudResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
func (r *kubernetesCloudResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "A resource that represent a Juju Cloud for existing controller.",
Attributes: map[string]schema.Attribute{
Expand Down Expand Up @@ -111,10 +111,10 @@ func (o *kubernetesCloudResource) Schema(_ context.Context, req resource.SchemaR
}

// Create adds a new kubernetes cloud to controllers used now by Terraform provider.
func (o *kubernetesCloudResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
func (r *kubernetesCloudResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Prevent panic if the provider has not been configured.
if o.client == nil {
addClientNotConfiguredError(&resp.Diagnostics, "ssh_key", "create")
if r.client == nil {
addClientNotConfiguredError(&resp.Diagnostics, "kubernetes_cloud", "create")
return
}

Expand All @@ -127,7 +127,7 @@ func (o *kubernetesCloudResource) Create(ctx context.Context, req resource.Creat
}

// Create the kubernetes cloud.
err := o.client.Clouds.CreateKubernetesCloud(
err := r.client.Clouds.CreateKubernetesCloud(
&juju.CreateKubernetesCloudInput{
Name: plan.CloudName.ValueString(),
KubernetesConfig: plan.KubernetesConfig.ValueString(),
Expand All @@ -138,7 +138,7 @@ func (o *kubernetesCloudResource) Create(ctx context.Context, req resource.Creat
return
}

o.trace(fmt.Sprintf("Created kubernetes cloud %s", plan.CloudName.ValueString()))
r.trace(fmt.Sprintf("Created kubernetes cloud %s", plan.CloudName.ValueString()))

plan.ID = types.StringValue(newKubernetesCloudID(plan.CloudName.ValueString()))
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
Expand All @@ -149,10 +149,10 @@ func newKubernetesCloudID(name string) string {
}

// Read reads the current state of the kubernetes cloud.
func (o *kubernetesCloudResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
func (r *kubernetesCloudResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// Prevent panic if the provider has not been configured.
if o.client == nil {
addClientNotConfiguredError(&resp.Diagnostics, "ssh_key", "read")
if r.client == nil {
addClientNotConfiguredError(&resp.Diagnostics, "kubernetes_cloud", "read")
return
}

Expand All @@ -165,7 +165,7 @@ func (o *kubernetesCloudResource) Read(ctx context.Context, req resource.ReadReq
}

// Read the kubernetes cloud.
cloud, err := o.client.Clouds.ReadKubernetesCloud(
cloud, err := r.client.Clouds.ReadKubernetesCloud(
&juju.ReadKubernetesCloudInput{
Name: plan.CloudName.ValueString(),
KubernetesConfig: plan.KubernetesConfig.ValueString(),
Expand All @@ -180,19 +180,47 @@ func (o *kubernetesCloudResource) Read(ctx context.Context, req resource.ReadReq
plan.ParentCloudRegion = types.StringValue(cloud.ParentCloudRegion)
plan.CloudName = types.StringValue(cloud.Name)
plan.KubernetesConfig = types.StringValue(cloud.KubernetesConfig)
plan.ID = types.StringValue(newKubernetesCloudID(cloud.Name))

// Set the plan onto the Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

// Update updates the kubernetes cloud on the controller used by Terraform provider.
func (o *kubernetesCloudResource) Update(context.Context, resource.UpdateRequest, *resource.UpdateResponse) {
func (r *kubernetesCloudResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// Prevent panic if the provider has not been configured.
if r.client == nil {
addClientNotConfiguredError(&resp.Diagnostics, "kubernetes_cloud", "update")
return
}

var plan kubernetesCloudResourceModel

// Read Terraform configuration from the request into the model
resp.Diagnostics.Append(req.State.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

// Update the kubernetes cloud.
err := r.client.Clouds.UpdateKubernetesCloud(
&juju.UpdateKubernetesCloudInput{
Name: plan.CloudName.ValueString(),
KubernetesConfig: plan.KubernetesConfig.ValueString(),
},
)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update kubernetes cloud, got error %s", err))
return
}

r.trace(fmt.Sprintf("Updated kubernetes cloud %s", plan.CloudName.ValueString()))
}

// Delete removes the kubernetes cloud from the controller used by Terraform provider.
func (o *kubernetesCloudResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
func (r *kubernetesCloudResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
// Prevent panic if the provider has not been configured.
if o.client == nil {
if r.client == nil {
addClientNotConfiguredError(&resp.Diagnostics, "kubernetes_cloud", "delete")
return
}
Expand All @@ -206,7 +234,7 @@ func (o *kubernetesCloudResource) Delete(ctx context.Context, req resource.Delet
}

// Remove the kubernetes cloud.
err := o.client.Clouds.RemoveKubernetesCloud(
err := r.client.Clouds.RemoveKubernetesCloud(
&juju.DestroyKubernetesCloudInput{
Name: plan.CloudName.ValueString(),
},
Expand All @@ -216,16 +244,16 @@ func (o *kubernetesCloudResource) Delete(ctx context.Context, req resource.Delet
return
}

o.trace(fmt.Sprintf("Removed kubernetes cloud %s", plan.CloudName.ValueString()))
r.trace(fmt.Sprintf("Removed kubernetes cloud %s", plan.CloudName.ValueString()))
}

func (o *kubernetesCloudResource) trace(msg string, additionalFields ...map[string]interface{}) {
if o.subCtx == nil {
func (r *kubernetesCloudResource) trace(msg string, additionalFields ...map[string]interface{}) {
if r.subCtx == nil {
return
}

//SubsystemTrace(subCtx, "my-subsystem", "hello, world", map[string]interface{}{"foo": 123})
// Output:
// {"@level":"trace","@message":"hello, world","@module":"provider.my-subsystem","foo":123}
tflog.SubsystemTrace(o.subCtx, LogResourceKubernetesCloud, msg, additionalFields...)
tflog.SubsystemTrace(r.subCtx, LogResourceKubernetesCloud, msg, additionalFields...)
}

0 comments on commit b106a60

Please sign in to comment.