add: internal package (provider, core, registrykeys)
This commit is contained in:
@@ -0,0 +1,491 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var _ resource.Resource = &QuickStartResource{}
|
||||
var _ resource.ResourceWithImportState = &QuickStartResource{}
|
||||
|
||||
func NewQuickStartResource() resource.Resource {
|
||||
return &QuickStartResource{}
|
||||
}
|
||||
|
||||
type QuickStartResource struct {
|
||||
client *NubesClient
|
||||
}
|
||||
|
||||
type QuickStartResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
DisplayName types.String `tfsdk:"display_name"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
Status types.String `tfsdk:"status"`
|
||||
|
||||
// Параметры из HAR файла
|
||||
OrgDomain types.String `tfsdk:"org_domain"` // 332: ngcloud.ru
|
||||
VAppName types.String `tfsdk:"vapp_name"` // 334: vappa
|
||||
ProviderVDC types.String `tfsdk:"provider_vdc"` // 358: sandbox-v1cl1-pvdc
|
||||
EdgeCount types.Int64 `tfsdk:"edge_count"` // 359: 1
|
||||
ExternalNetwork types.String `tfsdk:"external_network"` // 360: internet-ipv4-v1
|
||||
StorageProfiles types.String `tfsdk:"storage_profiles"` // 399: [{"name": "", "size": 1024}]
|
||||
NetworkPool types.String `tfsdk:"network_pool"` // 400: nsxt-sandbox-geneve-np
|
||||
CPUAllocationPercent types.Int64 `tfsdk:"cpu_allocation_percent"` // 401: 20
|
||||
RAMAllocationPercent types.Int64 `tfsdk:"ram_allocation_percent"` // 402: 20
|
||||
ServiceEngineGroup types.String `tfsdk:"service_engine_group"` // 403: SEGROUP-SANDBOX-CL1-SHARED-01
|
||||
ThinProvisioning types.Bool `tfsdk:"thin_provisioning"` // 404: true
|
||||
FastProvisioning types.Bool `tfsdk:"fast_provisioning"` // 405: false
|
||||
VDCNetworkQuota types.Int64 `tfsdk:"vdc_network_quota"` // 566: 1
|
||||
CPUQuotaMhz types.Int64 `tfsdk:"cpu_quota_mhz"` // 567: 10
|
||||
RAMQuotaGb types.Int64 `tfsdk:"ram_quota_gb"` // 568: 1
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_quick_start"
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
MarkdownDescription: "Nubes Quick Start - создаёт полное окружение (Org + VDC + Edge + vApp)",
|
||||
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
MarkdownDescription: "Quick Start identifier (UUID)",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"display_name": schema.StringAttribute{
|
||||
MarkdownDescription: "Display name",
|
||||
Required: true,
|
||||
},
|
||||
"description": schema.StringAttribute{
|
||||
MarkdownDescription: "Description",
|
||||
Optional: true,
|
||||
},
|
||||
"status": schema.StringAttribute{
|
||||
MarkdownDescription: "Current status",
|
||||
Computed: true,
|
||||
},
|
||||
"org_domain": schema.StringAttribute{
|
||||
MarkdownDescription: "Organization domain (например: ngcloud.ru)",
|
||||
Required: true,
|
||||
},
|
||||
"vapp_name": schema.StringAttribute{
|
||||
MarkdownDescription: "vApp name",
|
||||
Required: true,
|
||||
},
|
||||
"provider_vdc": schema.StringAttribute{
|
||||
MarkdownDescription: "Provider VDC name",
|
||||
Required: true,
|
||||
},
|
||||
"edge_count": schema.Int64Attribute{
|
||||
MarkdownDescription: "Number of Edge gateways",
|
||||
Required: true,
|
||||
},
|
||||
"external_network": schema.StringAttribute{
|
||||
MarkdownDescription: "External network name",
|
||||
Required: true,
|
||||
},
|
||||
"storage_profiles": schema.StringAttribute{
|
||||
MarkdownDescription: "Storage profiles JSON",
|
||||
Required: true,
|
||||
},
|
||||
"network_pool": schema.StringAttribute{
|
||||
MarkdownDescription: "Network pool name",
|
||||
Required: true,
|
||||
},
|
||||
"cpu_allocation_percent": schema.Int64Attribute{
|
||||
MarkdownDescription: "CPU allocation percent",
|
||||
Required: true,
|
||||
},
|
||||
"ram_allocation_percent": schema.Int64Attribute{
|
||||
MarkdownDescription: "RAM allocation percent",
|
||||
Required: true,
|
||||
},
|
||||
"service_engine_group": schema.StringAttribute{
|
||||
MarkdownDescription: "Service Engine Group",
|
||||
Required: true,
|
||||
},
|
||||
"thin_provisioning": schema.BoolAttribute{
|
||||
MarkdownDescription: "Enable thin provisioning",
|
||||
Required: true,
|
||||
},
|
||||
"fast_provisioning": schema.BoolAttribute{
|
||||
MarkdownDescription: "Enable fast provisioning",
|
||||
Required: true,
|
||||
},
|
||||
"vdc_network_quota": schema.Int64Attribute{
|
||||
MarkdownDescription: "VDC network quota",
|
||||
Required: true,
|
||||
},
|
||||
"cpu_quota_mhz": schema.Int64Attribute{
|
||||
MarkdownDescription: "CPU quota in MHz",
|
||||
Required: true,
|
||||
},
|
||||
"ram_quota_gb": schema.Int64Attribute{
|
||||
MarkdownDescription: "RAM quota in GB",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := req.ProviderData.(*NubesClient)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Resource Configure Type",
|
||||
fmt.Sprintf("Expected *NubesClient, got: %T", req.ProviderData),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
r.client = client
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var data QuickStartResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Step 1: Create instance
|
||||
instanceReq := CreateInstanceRequest{
|
||||
ServiceId: 113, // Quick Start service ID
|
||||
DisplayName: data.DisplayName.ValueString(),
|
||||
Descr: data.Description.ValueString(),
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(instanceReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to marshal instance request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", r.client.ApiEndpoint+"/instances", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create instance request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if r.client.ApiToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+r.client.ApiToken)
|
||||
}
|
||||
|
||||
httpResp, err := r.client.HttpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create instance: %s", err))
|
||||
return
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
if httpResp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(httpResp.Body)
|
||||
resp.Diagnostics.AddError("API Error", fmt.Sprintf("Create instance failed with status %d: %s", httpResp.StatusCode, string(body)))
|
||||
return
|
||||
}
|
||||
|
||||
location := httpResp.Header.Get("Location")
|
||||
if location == "" {
|
||||
resp.Diagnostics.AddError("API Error", "No Location header in response")
|
||||
return
|
||||
}
|
||||
instanceId := location[2:]
|
||||
data.ID = types.StringValue(instanceId)
|
||||
|
||||
// Step 2: Create operation
|
||||
operationReq := CreateOperationRequest{
|
||||
InstanceUid: instanceId,
|
||||
Operation: "create",
|
||||
}
|
||||
|
||||
jsonData, err = json.Marshal(operationReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to marshal operation request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq, err = http.NewRequestWithContext(ctx, "POST", r.client.ApiEndpoint+"/instanceOperations", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create operation request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if r.client.ApiToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+r.client.ApiToken)
|
||||
}
|
||||
|
||||
httpResp, err = r.client.HttpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create operation: %s", err))
|
||||
return
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
if httpResp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(httpResp.Body)
|
||||
resp.Diagnostics.AddError("API Error", fmt.Sprintf("Create operation failed with status %d: %s", httpResp.StatusCode, string(body)))
|
||||
return
|
||||
}
|
||||
|
||||
location = httpResp.Header.Get("Location")
|
||||
if location == "" {
|
||||
resp.Diagnostics.AddError("API Error", "No Location header in operation response")
|
||||
return
|
||||
}
|
||||
operationId := location[2:]
|
||||
|
||||
// Step 3: Submit all parameters
|
||||
params := map[int]string{
|
||||
332: data.OrgDomain.ValueString(),
|
||||
334: data.VAppName.ValueString(),
|
||||
358: data.ProviderVDC.ValueString(),
|
||||
359: fmt.Sprintf("%d", data.EdgeCount.ValueInt64()),
|
||||
360: data.ExternalNetwork.ValueString(),
|
||||
399: data.StorageProfiles.ValueString(),
|
||||
400: data.NetworkPool.ValueString(),
|
||||
401: fmt.Sprintf("%d", data.CPUAllocationPercent.ValueInt64()),
|
||||
402: fmt.Sprintf("%d", data.RAMAllocationPercent.ValueInt64()),
|
||||
403: data.ServiceEngineGroup.ValueString(),
|
||||
404: fmt.Sprintf("%t", data.ThinProvisioning.ValueBool()),
|
||||
405: fmt.Sprintf("%t", data.FastProvisioning.ValueBool()),
|
||||
566: fmt.Sprintf("%d", data.VDCNetworkQuota.ValueInt64()),
|
||||
567: fmt.Sprintf("%d", data.CPUQuotaMhz.ValueInt64()),
|
||||
568: fmt.Sprintf("%d", data.RAMQuotaGb.ValueInt64()),
|
||||
}
|
||||
|
||||
for paramId, paramValue := range params {
|
||||
paramReq := CreateCfsParamRequest{
|
||||
InstanceOperationUid: operationId,
|
||||
SvcOperationCfsParamId: paramId,
|
||||
ParamValue: paramValue,
|
||||
}
|
||||
|
||||
jsonData, err = json.Marshal(paramReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to marshal param request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq, err = http.NewRequestWithContext(ctx, "POST", r.client.ApiEndpoint+"/instanceOperationCfsParams", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create param request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if r.client.ApiToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+r.client.ApiToken)
|
||||
}
|
||||
|
||||
httpResp, err = r.client.HttpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to submit parameter: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
respBody, _ := io.ReadAll(httpResp.Body)
|
||||
httpResp.Body.Close()
|
||||
|
||||
if httpResp.StatusCode != http.StatusCreated && httpResp.StatusCode != http.StatusOK {
|
||||
resp.Diagnostics.AddError("API Error", fmt.Sprintf("Submit parameter %d failed with status %d: %s", paramId, httpResp.StatusCode, string(respBody)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Run operation
|
||||
httpReq, err = http.NewRequestWithContext(ctx, "POST", r.client.ApiEndpoint+"/instanceOperations/"+operationId+"/run", bytes.NewBuffer([]byte("{}")))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create run request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if r.client.ApiToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+r.client.ApiToken)
|
||||
}
|
||||
|
||||
httpResp, err = r.client.HttpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to run operation: %s", err))
|
||||
return
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
if httpResp.StatusCode != http.StatusOK && httpResp.StatusCode != http.StatusCreated && httpResp.StatusCode != http.StatusAccepted {
|
||||
body, _ := io.ReadAll(httpResp.Body)
|
||||
resp.Diagnostics.AddError("API Error", fmt.Sprintf("Run operation failed with status %d: %s", httpResp.StatusCode, string(body)))
|
||||
return
|
||||
}
|
||||
|
||||
// Wait and read status
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
readData, err := r.readInstance(ctx, instanceId)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read instance after creation: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
data.Status = types.StringValue(readData.Status)
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var data QuickStartResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
instanceResp, err := r.readInstance(ctx, data.ID.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read instance: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
data.Status = types.StringValue(instanceResp.Status)
|
||||
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
resp.Diagnostics.AddError("Not Supported", "Quick Start resource cannot be updated")
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var data QuickStartResourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
operationReq := CreateOperationRequest{
|
||||
InstanceUid: data.ID.ValueString(),
|
||||
Operation: "delete",
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(operationReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to marshal operation request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", r.client.ApiEndpoint+"/instanceOperations", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create operation request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if r.client.ApiToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+r.client.ApiToken)
|
||||
}
|
||||
|
||||
httpResp, err := r.client.HttpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create delete operation: %s", err))
|
||||
return
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
if httpResp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(httpResp.Body)
|
||||
resp.Diagnostics.AddError("API Error", fmt.Sprintf("Create delete operation failed with status %d: %s", httpResp.StatusCode, string(body)))
|
||||
return
|
||||
}
|
||||
|
||||
location := httpResp.Header.Get("Location")
|
||||
if location == "" {
|
||||
resp.Diagnostics.AddError("API Error", "No Location header in operation response")
|
||||
return
|
||||
}
|
||||
operationId := location[2:]
|
||||
|
||||
// Run delete operation
|
||||
httpReq, err = http.NewRequestWithContext(ctx, "POST", r.client.ApiEndpoint+"/instanceOperations/"+operationId+"/run", bytes.NewBuffer([]byte("{}")))
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create run request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if r.client.ApiToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+r.client.ApiToken)
|
||||
}
|
||||
|
||||
httpResp, err = r.client.HttpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to run delete operation: %s", err))
|
||||
return
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
if httpResp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(httpResp.Body)
|
||||
resp.Diagnostics.AddError("API Error", fmt.Sprintf("Run delete operation failed with status %d: %s", httpResp.StatusCode, string(body)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
|
||||
func (r *QuickStartResource) readInstance(ctx context.Context, instanceId string) (*InstanceResponse, error) {
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "GET", r.client.ApiEndpoint+"/instances/"+instanceId, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.client.ApiToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+r.client.ApiToken)
|
||||
}
|
||||
|
||||
httpResp, err := r.client.HttpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(httpResp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if httpResp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("read request failed with status %d: %s", httpResp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var instanceResp InstanceResponse
|
||||
if err := json.Unmarshal(body, &instanceResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &instanceResp, nil
|
||||
}
|
||||
Reference in New Issue
Block a user