-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update kubeconfig when invalid #627
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ type Model struct { | |
Expiration types.Int64 `tfsdk:"expiration"` | ||
Refresh types.Bool `tfsdk:"refresh"` | ||
ExpiresAt types.String `tfsdk:"expires_at"` | ||
CreationTime types.String `tfsdk:"creation_time"` | ||
} | ||
|
||
// NewKubeconfigResource is a helper function to simplify the provider implementation. | ||
|
@@ -108,6 +109,7 @@ func (r *kubeconfigResource) Schema(_ context.Context, _ resource.SchemaRequest, | |
"expiration": "Expiration time of the kubeconfig, in seconds. Defaults to `3600`", | ||
"expires_at": "Timestamp when the kubeconfig expires", | ||
"refresh": "If set to true, the provider will check if the kubeconfig has expired and will generated a new valid one in-place", | ||
"creation_time": "Date-time when the kubeconfig was created", | ||
} | ||
|
||
resp.Schema = schema.Schema{ | ||
|
@@ -182,6 +184,13 @@ func (r *kubeconfigResource) Schema(_ context.Context, _ resource.SchemaRequest, | |
stringplanmodifier.UseStateForUnknown(), | ||
}, | ||
}, | ||
"creation_time": schema.StringAttribute{ | ||
Description: descriptions["creation_time"], | ||
Computed: true, | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.RequiresReplace(), | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
@@ -230,6 +239,8 @@ func (r *kubeconfigResource) Create(ctx context.Context, req resource.CreateRequ | |
// Read refreshes the Terraform state with the latest data. | ||
// There is no GET kubeconfig endpoint. | ||
// If the refresh field is set, Read will check the expiration date and will get a new valid kubeconfig if it has expired | ||
// If kubeconfig creation time is before lastCompletionTime of the credentials rotation or | ||
// before cluster creation time a new kubeconfig is created. | ||
func (r *kubeconfigResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform | ||
// Retrieve values from plan | ||
var model Model | ||
|
@@ -246,6 +257,21 @@ func (r *kubeconfigResource) Read(ctx context.Context, req resource.ReadRequest, | |
ctx = tflog.SetField(ctx, "cluster_name", clusterName) | ||
ctx = tflog.SetField(ctx, "kube_config_id", kubeconfigUUID) | ||
|
||
cluster, err := r.client.GetClusterExecute(ctx, projectId, clusterName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would recommend to extract helper functions for this and the following block to keep this function a little bit shorter (and facilitate testing). |
||
if err != nil { | ||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading kubeconfig", fmt.Sprintf("Could not get cluster(%s): %v", clusterName, err)) | ||
return | ||
} | ||
|
||
creationTime, err := time.Parse("2006-01-02T15:04:05Z07:00", model.CreationTime.ValueString()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the pattern is the same as time.RFC3339, we should use that one |
||
if err != nil { | ||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading kubeconfig", fmt.Sprintf("Converting creationTime field to timestamp: %v", err)) | ||
return | ||
} | ||
|
||
recreateKubeconfig := false | ||
|
||
// check if kubeconfig has expired | ||
if model.Refresh.ValueBool() && !model.ExpiresAt.IsNull() { | ||
expiresAt, err := time.Parse("2006-01-02T15:04:05Z07:00", model.ExpiresAt.ValueString()) | ||
if err != nil { | ||
|
@@ -254,18 +280,48 @@ func (r *kubeconfigResource) Read(ctx context.Context, req resource.ReadRequest, | |
} | ||
currentTime := time.Now() | ||
if expiresAt.Before(currentTime) { | ||
err := r.createKubeconfig(ctx, &model) | ||
if err != nil { | ||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading kubeconfig", fmt.Sprintf("The existing kubeconfig is expired and the refresh field is enabled, creating a new one: %v", err)) | ||
return | ||
} | ||
|
||
// Set state to fully populated data | ||
diags = resp.State.Set(ctx, model) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
recreateKubeconfig = true | ||
} | ||
} | ||
|
||
// check credentials rotation | ||
if cluster.Status.CredentialsRotation.LastCompletionTime != nil { | ||
lastCompletionTime, err := time.Parse("2006-01-02T15:04:05Z07:00", *cluster.Status.CredentialsRotation.LastCompletionTime) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see above, time.RFC3389 |
||
if err != nil { | ||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading kubeconfig", fmt.Sprintf("Converting LastCompletionTime to timestamp: %v", err)) | ||
return | ||
} | ||
|
||
if creationTime.Before(lastCompletionTime) { | ||
recreateKubeconfig = true | ||
} | ||
} | ||
|
||
// check cluster recreation | ||
if cluster.Status.CreationTime != nil { | ||
clusterCreationTime, err := time.Parse("2006-01-02T15:04:05Z07:00", *cluster.Status.CreationTime) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. time.RFC3389 |
||
if err != nil { | ||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading kubeconfig", fmt.Sprintf("Converting clusterCreationTime to timestamp: %v", err)) | ||
return | ||
} | ||
|
||
if creationTime.Before(clusterCreationTime) { | ||
recreateKubeconfig = true | ||
} | ||
} | ||
|
||
if recreateKubeconfig { | ||
err := r.createKubeconfig(ctx, &model) | ||
if err != nil { | ||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading kubeconfig", fmt.Sprintf("The existing kubeconfig is invalid, creating a new one: %v", err)) | ||
return | ||
} | ||
|
||
// Set state to fully populated data | ||
diags = resp.State.Set(ctx, model) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
} | ||
|
||
|
@@ -285,7 +341,7 @@ func (r *kubeconfigResource) createKubeconfig(ctx context.Context, model *Model) | |
} | ||
|
||
// Map response body to schema | ||
err = mapFields(kubeconfigResp, model) | ||
err = mapFields(kubeconfigResp, model, time.Now()) | ||
if err != nil { | ||
return fmt.Errorf("processing API payload: %w", err) | ||
} | ||
|
@@ -320,7 +376,7 @@ func (r *kubeconfigResource) Delete(ctx context.Context, req resource.DeleteRequ | |
tflog.Info(ctx, "SKE kubeconfig deleted") | ||
} | ||
|
||
func mapFields(kubeconfigResp *ske.Kubeconfig, model *Model) error { | ||
func mapFields(kubeconfigResp *ske.Kubeconfig, model *Model, creationTime time.Time) error { | ||
if kubeconfigResp == nil { | ||
return fmt.Errorf("response is nil") | ||
} | ||
|
@@ -343,6 +399,8 @@ func mapFields(kubeconfigResp *ske.Kubeconfig, model *Model) error { | |
|
||
model.Kubeconfig = types.StringPointerValue(kubeconfigResp.Kubeconfig) | ||
model.ExpiresAt = types.StringPointerValue(kubeconfigResp.ExpirationTimestamp) | ||
// set creation time | ||
model.CreationTime = types.StringValue(creationTime.Format(time.RFC3339)) | ||
return nil | ||
} | ||
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would propose to add testcases for the added functionality (which would have to extracted to dedicated functions first) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not to sure here. Wouldn't RequiresReplace force a recreation of the cluster, when the creation time has been updated? I have'nt tested it though