diff --git a/commons/api_path.go b/commons/api_path.go index dd6db12..36bc610 100644 --- a/commons/api_path.go +++ b/commons/api_path.go @@ -40,6 +40,7 @@ var ApiPath = struct { ListSubnets func(vpcId string) string Subnet func(vpcId string) string + DedicatedFKEList func(vpcId string, page, pageSize int) string DedicatedFKEGet func(vpcId string, clusterId string) string DedicatedFKEUpgradeVersion func(vpcId string, clusterId string) string DedicatedFKEManagement func(vpcId string, clusterId string) string @@ -153,6 +154,9 @@ var ApiPath = struct { Subnet: func(vpcId string) string { return fmt.Sprintf("/v1/vmware/vpc/%s/network/subnets", vpcId) }, + DedicatedFKEList: func(vpcId string, page, pageSize int) string { + return fmt.Sprintf("/v1/xplat/fke/vpc/%s/kubernetes?page=%d&page_size=%d", vpcId, page, pageSize) + }, DedicatedFKEGet: func(vpcId string, clusterId string) string { return fmt.Sprintf("/v1/xplat/fke/vpc/%s/cluster/%s?page=1&page_size=25", vpcId, clusterId) }, diff --git a/fptcloud/dfke/datasource_dfke.go b/fptcloud/dfke/datasource_dfke.go new file mode 100644 index 0000000..4e46018 --- /dev/null +++ b/fptcloud/dfke/datasource_dfke.go @@ -0,0 +1,296 @@ +package fptcloud_dfke + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + diag2 "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "terraform-provider-fptcloud/commons" +) + +var ( + _ datasource.DataSource = &datasourceDedicatedKubernetesEngine{} + _ datasource.DataSourceWithConfigure = &datasourceDedicatedKubernetesEngine{} +) + +type datasourceDedicatedKubernetesEngine struct { + client *commons.Client + dfkeClient *dfkeApiClient + tenancyApiClient *tenancyApiClient +} + +func NewDataSourceDedicatedKubernetesEngine() datasource.DataSource { + return &datasourceDedicatedKubernetesEngine{} +} + +func (d *datasourceDedicatedKubernetesEngine) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + + client, ok := request.ProviderData.(*commons.Client) + if !ok { + response.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *commons.Client, got: %T. Please report this issue to the provider developers.", request.ProviderData), + ) + + return + } + + d.client = client + a, err := newDfkeApiClient(client) + if err != nil { + response.Diagnostics.AddError( + "Error configuring API client", + fmt.Sprintf("%s", err.Error()), + ) + + return + } + + d.dfkeClient = a + + t := newTenancyApiClient(client) + d.tenancyApiClient = t +} + +func (d *datasourceDedicatedKubernetesEngine) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_dedicated_kubernetes_engine_v1" +} + +func (d *datasourceDedicatedKubernetesEngine) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Retrieves information about dedicated FKE clusters", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "vpc_id": schema.StringAttribute{ + Required: true, + }, + "cluster_id": schema.StringAttribute{ + Required: true, + Description: "Cluster ID, as shown on the dashboard, usually has a length of 8 characters", + }, + "cluster_name": schema.StringAttribute{ + Computed: true, + }, + "k8s_version": schema.StringAttribute{ + Computed: true, + }, + "master_type": schema.StringAttribute{ + Computed: true, + }, + "master_count": schema.Int64Attribute{ + Computed: true, + }, + "master_disk_size": schema.Int64Attribute{ + Computed: true, + }, + "worker_type": schema.StringAttribute{ + Computed: true, + }, + "worker_disk_size": schema.Int64Attribute{ + Computed: true, + }, + "network_id": schema.StringAttribute{ + Computed: true, + }, + "lb_size": schema.StringAttribute{ + Computed: true, + }, + "pod_network": schema.StringAttribute{ + Computed: true, + }, + "service_network": schema.StringAttribute{ + Computed: true, + }, + "network_node_prefix": schema.Int64Attribute{ + Computed: true, + }, + "max_pod_per_node": schema.Int64Attribute{ + Computed: true, + }, + "nfs_status": schema.StringAttribute{ + Computed: true, + }, + "nfs_disk_size": schema.Int64Attribute{ + Computed: true, + }, + "storage_policy": schema.StringAttribute{ + Computed: true, + }, + "edge_id": schema.StringAttribute{ + Computed: true, + }, + "scale_min": schema.Int64Attribute{ + Computed: true, + }, + "scale_max": schema.Int64Attribute{ + Computed: true, + }, + "node_dns": schema.StringAttribute{ + Computed: true, + }, + "ip_public_firewall": schema.StringAttribute{ + Computed: true, + }, + "ip_private_firewall": schema.StringAttribute{ + Computed: true, + }, + "region_id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (d *datasourceDedicatedKubernetesEngine) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + var state dedicatedKubernetesEngine + diags := request.Config.Get(ctx, &state) + + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + clusterId := state.ClusterId.ValueString() + uuid, err := d.findClusterUUID(ctx, state.vpcId(), clusterId) + if err != nil { + response.Diagnostics.Append(diag2.NewErrorDiagnostic("Error resolving cluster UUID", err.Error())) + return + } + + _, err = d.internalRead(ctx, uuid, &state) + if err != nil { + response.Diagnostics.Append(diag2.NewErrorDiagnostic("Error calling API", err.Error())) + return + } + + diags = response.State.Set(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } +} + +func (d *datasourceDedicatedKubernetesEngine) internalRead(ctx context.Context, clusterId string, state *dedicatedKubernetesEngine) (*dedicatedKubernetesEngineReadResponse, error) { + vpcId := state.VpcId.ValueString() + tflog.Info(ctx, "Reading state of cluster ID "+clusterId+", VPC ID "+vpcId) + + a, err := d.client.SendGetRequest(commons.ApiPath.DedicatedFKEGet(vpcId, clusterId)) + + if err != nil { + return nil, err + } + + var readResponse dedicatedKubernetesEngineReadResponse + err = json.Unmarshal(a, &readResponse) + if err != nil { + tflog.Info(ctx, "Error unmarshalling cluster info for cluster "+clusterId) + return nil, err + } + data := readResponse.Cluster + + var awx dedicatedKubernetesEngineParams + err = json.Unmarshal([]byte(data.AwxParams), &awx) + + if err != nil { + tflog.Info(ctx, "Error unmarshalling AWX params for cluster "+clusterId) + tflog.Info(ctx, "AwxParams is "+data.AwxParams) + return nil, err + } + + // resolve edge ID + edge, err := d.dfkeClient.FindEdgeByEdgeGatewayId(ctx, vpcId, data.EdgeID) + if err != nil { + return nil, err + } + + state.ClusterId = types.StringValue(data.ClusterID) + state.ClusterName = types.StringValue(data.Name) + state.Version = types.StringValue(awx.K8SVersion) + state.MasterType = types.StringValue(awx.MasterType) + state.MasterCount = types.Int64Value(int64(awx.MasterCount)) + state.MasterDiskSize = types.Int64Value(int64(awx.MasterDiskSize)) + state.WorkerType = types.StringValue(awx.WorkerType) + state.WorkerDiskSize = types.Int64Value(int64(awx.WorkerDiskSize)) + state.NetworkID = types.StringValue(data.NetworkID) + state.LbSize = types.StringValue(awx.LbSize) + state.PodNetwork = types.StringValue(awx.PodNetwork + "/" + awx.PodPrefix) + state.ServiceNetwork = types.StringValue(awx.ServiceNetwork + "/" + awx.ServicePrefix) + state.NetworkNodePrefix = types.Int64Value(int64(awx.NetworkNodePrefix)) + state.MaxPodPerNode = types.Int64Value(int64(awx.K8SMaxPod)) + state.NfsStatus = types.StringValue(awx.NfsStatus) + state.NfsDiskSize = types.Int64Value(int64(awx.NfsDiskSize)) + state.StoragePolicy = types.StringValue(awx.StorageProfile) + state.EdgeID = types.StringValue(edge.Id) + state.ScaleMin = types.Int64Value(int64(awx.ScaleMinSize)) + state.ScaleMax = types.Int64Value(int64(awx.ScaleMaxSize)) + state.NodeDNS = types.StringValue(awx.NodeDNS) + state.IPPublicFirewall = types.StringValue(awx.IPPublicFirewall) + state.IPPrivateFirewall = types.StringValue(awx.IPPrivateFirewall) + state.VpcId = types.StringValue(data.VpcID) + //state.CustomScript = awx.CustomScript + //state.EnableCustomScript = awx.EnableCustomScript + region, err := getRegionFromVpcId(d.tenancyApiClient, ctx, vpcId) + if err != nil { + return nil, err + } + state.RegionId = types.StringValue(region) + + return &readResponse, nil +} + +func (d *datasourceDedicatedKubernetesEngine) findClusterUUID(ctx context.Context, vpcId string, clusterId string) (string, error) { + total := 1 + found := 0 + + index := 1 + for found < total { + path := commons.ApiPath.DedicatedFKEList(vpcId, index, 25) + data, err := d.client.SendGetRequest(path) + if err != nil { + return "", err + } + + var list dedicatedKubernetesEngineList + err = json.Unmarshal(data, &list) + if err != nil { + return "", err + } + + if list.Total == 0 { + return "", errors.New("no cluster with such ID found") + } + + if len(list.Data) == 0 { + return "", errors.New("no cluster with such ID found") + } + + total = list.Total + index += 1 + for _, entry := range list.Data { + if entry.ClusterId == clusterId { + return entry.Id, nil + } + } + } + + return "", errors.New("no cluster with such ID found") +} + +type dedicatedKubernetesEngineList struct { + Data []struct { + ClusterName string `json:"cluster_name"` + ClusterId string `json:"cluster_id,omitempty"` + Id string `json:"id,omitempty"` + } `json:"data"` + Total int `json:"total"` +} diff --git a/fptcloud/dfke/dfke_service.go b/fptcloud/dfke/dfke_service.go index 0db231c..43064dc 100644 --- a/fptcloud/dfke/dfke_service.go +++ b/fptcloud/dfke/dfke_service.go @@ -44,6 +44,10 @@ type edgeResponse struct { EdgeGateway EdgeGateway `json:"edgeGateway"` } +type edgeListResponse struct { + Data []EdgeGateway `json:"data"` +} + func (a *dfkeApiClient) FindEdgeById(ctx context.Context, vpcId string, id string) (*EdgeGateway, error) { tflog.Info(ctx, "Resolving edge by ID "+id) path := fmt.Sprintf("/v1/kubernetes/vpc/%s/find_edge_by_id/%s/false", vpcId, id) @@ -59,14 +63,28 @@ func (a *dfkeApiClient) FindEdgeByEdgeGatewayId(ctx context.Context, vpcId strin if !strings.HasPrefix(edgeId, "urn:vcloud:gateway") { return nil, errors.New("edge gateway id must be prefixed with \"urn:vcloud:gateway\"") } + tflog.Info(ctx, "Resolving edge by gateway ID "+edgeId) - path := fmt.Sprintf("/v1/kubernetes/vpc/%s/find_edge_by_id/%s/true", vpcId, edgeId) - r, err := a.internalFindEdge(path) + + path := fmt.Sprintf("/v1/vmware/vpc/%s/edge_gateway/list", vpcId) + r, err := a.edgeClient.SendGetRequest(path) if err != nil { return nil, err } - return r, nil + var edgeList edgeListResponse + err = json.Unmarshal(r, &edgeList) + if err != nil { + return nil, err + } + + for _, edge := range edgeList.Data { + if edge.EdgeGatewayId == edgeId { + return &edge, nil + } + } + + return nil, errors.New("edge gateway not found") } func (a *dfkeApiClient) internalFindEdge(endpoint string) (*EdgeGateway, error) { diff --git a/fptcloud/dfke/resource_dfke.go b/fptcloud/dfke/resource_dfke.go index c7d75eb..3750996 100644 --- a/fptcloud/dfke/resource_dfke.go +++ b/fptcloud/dfke/resource_dfke.go @@ -415,7 +415,7 @@ func (r *resourceDedicatedKubernetesEngine) internalRead(ctx context.Context, cl state.VpcId = types.StringValue(data.VpcID) //state.CustomScript = awx.CustomScript //state.EnableCustomScript = awx.EnableCustomScript - region, err := r.getRegionFromVpcId(ctx, vpcId) + region, err := getRegionFromVpcId(r.tenancyApiClient, ctx, vpcId) if err != nil { return nil, err } @@ -746,9 +746,7 @@ func (e *dedicatedKubernetesEngine) vpcId() string { func (e *dedicatedKubernetesEngine) clusterUUID() string { return e.Id.ValueString() } -func (r *resourceDedicatedKubernetesEngine) getRegionFromVpcId(ctx context.Context, vpcId string) (string, error) { - client := r.tenancyApiClient - +func getRegionFromVpcId(client *tenancyApiClient, ctx context.Context, vpcId string) (string, error) { t, err := client.GetTenancy(ctx) if err != nil { return "", err diff --git a/fptcloud/provider_tf6.go b/fptcloud/provider_tf6.go index 6a7c91b..2ac2dae 100644 --- a/fptcloud/provider_tf6.go +++ b/fptcloud/provider_tf6.go @@ -147,7 +147,9 @@ func (x *xplatProvider) Configure(ctx context.Context, request provider.Configur } func (x *xplatProvider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + fptcloud_dfke.NewDataSourceDedicatedKubernetesEngine, + } } func (x *xplatProvider) Resources(ctx context.Context) []func() resource.Resource { diff --git a/fptcloud/subnet/subnetClient.go b/fptcloud/subnet/subnetClient.go new file mode 100644 index 0000000..25266e4 --- /dev/null +++ b/fptcloud/subnet/subnetClient.go @@ -0,0 +1,44 @@ +package fptcloud_subnet + +import ( + "encoding/json" + "terraform-provider-fptcloud/commons" +) + +type SubnetClient struct { + *commons.Client +} + +func NewSubnetClient(client *commons.Client) *SubnetClient { + return &SubnetClient{client} +} + +func (c *SubnetClient) ListNetworks(vpcId string) ([]SubnetData, error) { + url := commons.ApiPath.Subnet(vpcId) + res, err := c.SendGetRequest(url) + + if err != nil { + return nil, err + } + + var r subnetResponse + if err = json.Unmarshal(res, &r); err != nil { + return nil, err + } + + return r.Data, nil +} + +type SubnetData struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DefaultGateway string `json:"defaultGateway"` + SubnetPrefixLength int `json:"subnetPrefixLength"` + NetworkID interface{} `json:"network_id"` + NetworkType string `json:"networkType"` +} + +type subnetResponse struct { + Data []SubnetData `json:"data"` +}