From cfce8e0abea46e9a9f6a6e014bd3486f0e46d667 Mon Sep 17 00:00:00 2001 From: PoAn Yang Date: Mon, 12 Aug 2024 11:37:29 +0800 Subject: [PATCH] feat: add harvester_bootstrap Signed-off-by: PoAn Yang --- internal/config/config.go | 40 ++++ .../provider/bootstrap/resource_bootstrap.go | 182 ++++++++++++++++++ .../provider/bootstrap/schema_bootstrap.go | 43 +++++ .../datasource_clusternetwork.go | 7 +- .../clusternetwork/resource_clusternetwork.go | 22 ++- internal/provider/image/datasource_image.go | 7 +- internal/provider/image/resource_image.go | 27 ++- .../provider/keypair/datasource_keypair.go | 7 +- internal/provider/keypair/resource_keypair.go | 22 ++- .../provider/network/datasource_network.go | 7 +- internal/provider/network/resource_network.go | 22 ++- internal/provider/provider.go | 66 +++++-- .../storageclass/datasource_storageclass.go | 7 +- .../storageclass/resource_storageclass.go | 22 ++- .../datasource_virtualmachine.go | 7 +- .../virtualmachine/resource_virtualmachine.go | 69 ++++--- .../vlanconfig/datasource_vlanconfig.go | 7 +- .../vlanconfig/resource_vlanconfig.go | 22 ++- internal/provider/volume/datasource_volume.go | 7 +- internal/provider/volume/resource_volume.go | 22 ++- internal/util/http.go | 129 +++++++++++++ pkg/constants/constants.go | 2 + pkg/constants/constants_bootstrap.go | 10 + 23 files changed, 659 insertions(+), 97 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/provider/bootstrap/resource_bootstrap.go create mode 100644 internal/provider/bootstrap/schema_bootstrap.go create mode 100644 internal/util/http.go create mode 100644 pkg/constants/constants_bootstrap.go diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..9ee54699 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,40 @@ +package config + +import ( + "context" + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/harvester/terraform-provider-harvester/pkg/client" +) + +type Config struct { + Bootstrap bool + APIURL string + KubeConfig string + KubeContext string +} + +func (c *Config) K8sClient() (*client.Client, error) { + return client.NewClient(c.KubeConfig, c.KubeContext) +} + +func (c *Config) CheckVersion() error { + client, err := c.K8sClient() + if err != nil { + return err + } + + // check harvester version from settings + serverVersion, err := client.HarvesterClient.HarvesterhciV1beta1().Settings().Get(context.Background(), "server-version", metav1.GetOptions{}) + if err != nil { + return err + } + // harvester version v1.0-head, v1.0.2, v1.0.3 is not supported + if strings.HasPrefix(serverVersion.Value, "v1.0") { + return fmt.Errorf("current Harvester server version is %s, the minimum supported version is v1.1.0", serverVersion.Value) + } + return nil +} diff --git a/internal/provider/bootstrap/resource_bootstrap.go b/internal/provider/bootstrap/resource_bootstrap.go new file mode 100644 index 00000000..13f5170e --- /dev/null +++ b/internal/provider/bootstrap/resource_bootstrap.go @@ -0,0 +1,182 @@ +package bootstrap + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/harvester/terraform-provider-harvester/internal/config" + "github.com/harvester/terraform-provider-harvester/internal/util" + "github.com/harvester/terraform-provider-harvester/pkg/constants" +) + +const ( + bootstrapDefaultUser = "admin" + bootstrapDefaultTTL = "60000" + bootstrapDefaultSessionDesc = "Terraform bootstrap admin session" +) + +func ResourceBootstrap() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceBootstrapCreate, + ReadContext: resourceBootstrapRead, + DeleteContext: resourceBootstrapDelete, + Schema: Schema(), + } +} + +func resourceBootstrapCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*config.Config) + if !c.Bootstrap { + return diag.FromErr(fmt.Errorf("harvester_bootstrap just available on bootstrap mode")) + } + initialPassword := d.Get(constants.FieldBootstrapInitialPassword).(string) + password := d.Get(constants.FieldBootstrapPassword).(string) + + // login to get token + tokenID, token, err := bootstrapLogin(d, c) + if err != nil { + return diag.FromErr(err) + } + d.SetId(tokenID) + + // change password + log.Printf("Doing change password") + if d.Get(constants.FieldShouldUpdatePassword).(bool) { + changePasswordURL := fmt.Sprintf("%s/%s", c.APIURL, "v3/users?action=changepassword") + changePasswordData := `{"currentPassword":"` + initialPassword + `","newPassword":"` + password + `"}` + changePasswordResp, err := util.DoPost(changePasswordURL, changePasswordData, "", true, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)}) + if err != nil { + return diag.FromErr(err) + } + if changePasswordResp.StatusCode != http.StatusOK { + return diag.Errorf("failed to change password, status code %d", changePasswordResp.StatusCode) + } + } + + // get kubeconfig + log.Printf("Doing generate kubeconfig") + genKubeConfigURL := fmt.Sprintf("%s/%s", c.APIURL, "v1/management.cattle.io.clusters/local?action=generateKubeconfig") + genKubeConfigResp, err := util.DoPost(genKubeConfigURL, "", "", true, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)}) + if err != nil { + return diag.FromErr(err) + } + if genKubeConfigResp.StatusCode != http.StatusOK { + return diag.Errorf("failed to generate kubeconfig, status code %d", genKubeConfigResp.StatusCode) + } + + genKubeConfigBody, err := util.GetJSONBody(genKubeConfigResp) + if err != nil { + return diag.FromErr(err) + } + if genKubeConfigBody["config"] == nil { + return diag.FromErr(fmt.Errorf("failed to generate kubeconfig")) + } + kubeConfigContent := genKubeConfigBody["config"].(string) + + // write kubeconfig + if err = os.WriteFile(d.Get(constants.FieldProviderKubeConfig).(string), []byte(kubeConfigContent), 0600); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceBootstrapRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*config.Config) + if !c.Bootstrap { + return diag.FromErr(fmt.Errorf("[ERROR] harvester_bootstrap just available on bootstrap mode")) + } + + // login to get token + _, token, err := bootstrapLogin(d, c) + if err != nil { + log.Printf("[INFO] Bootstrap is unable to login to Harvester") + d.SetId("") + return diag.FromErr(err) + } + + log.Printf("Doing generate kubeconfig") + genKubeConfigURL := fmt.Sprintf("%s/%s", c.APIURL, "v1/management.cattle.io.clusters/local?action=generateKubeconfig") + genKubeConfigResp, err := util.DoPost(genKubeConfigURL, "", "", true, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)}) + if err != nil { + return diag.FromErr(err) + } + if genKubeConfigResp.StatusCode != http.StatusOK { + return diag.Errorf("failed to generate kubeconfig, status code %d", genKubeConfigResp.StatusCode) + } + + genKubeConfigBody, err := util.GetJSONBody(genKubeConfigResp) + if err != nil { + return diag.FromErr(err) + } + if genKubeConfigBody["config"] == nil { + return diag.FromErr(fmt.Errorf("failed to generate kubeconfig")) + } + kubeConfigContent := genKubeConfigBody["config"].(string) + + // write kubeconfig + if err = os.WriteFile(d.Get(constants.FieldProviderKubeConfig).(string), []byte(kubeConfigContent), 0600); err != nil { + return diag.FromErr(err) + } + return nil +} + +func resourceBootstrapDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + d.SetId("") + return nil +} + +func bootstrapLogin(d *schema.ResourceData, c *config.Config) (string, string, error) { + initialPassword := d.Get(constants.FieldBootstrapInitialPassword).(string) + + log.Printf("Doing login with initial password") + tokenID, token, err := doUserLogin(c.APIURL, bootstrapDefaultUser, initialPassword, bootstrapDefaultTTL, bootstrapDefaultSessionDesc, "", true) + if err == nil { + d.Set(constants.FieldShouldUpdatePassword, true) + return tokenID, token, nil + } + + log.Printf("Doing login with password") + password := d.Get(constants.FieldBootstrapPassword).(string) + tokenID, token, err = doUserLogin(c.APIURL, bootstrapDefaultUser, password, bootstrapDefaultTTL, bootstrapDefaultSessionDesc, "", true) + if err == nil { + d.Set(constants.FieldShouldUpdatePassword, false) + return tokenID, token, nil + } + return "", "", err +} + +func doUserLogin(url, user, pass, ttl, desc, cacert string, insecure bool) (string, string, error) { + loginURL := url + "/v3-public/localProviders/local?action=login" + loginData := `{"username": "` + user + `", "password": "` + pass + `", "ttl": ` + ttl + `, "description": "` + desc + `"}` + loginHead := map[string]string{ + "Accept": "application/json", + "Content-Type": "application/json", + } + + // Login with user and pass + loginResp, err := util.DoPost(loginURL, loginData, cacert, insecure, loginHead) + if err != nil { + return "", "", err + } + if loginResp.StatusCode != http.StatusCreated { + return "", "", fmt.Errorf("can't login successfully, status code %d", loginResp.StatusCode) + } + + loginBody, err := util.GetJSONBody(loginResp) + if err != nil { + return "", "", err + } + + if loginBody["type"].(string) != "token" || loginBody["token"] == nil { + return "", "", fmt.Errorf("doing user logging: %s %s", loginBody["type"].(string), loginBody["code"].(string)) + } + + return loginBody["id"].(string), loginBody["token"].(string), nil +} diff --git a/internal/provider/bootstrap/schema_bootstrap.go b/internal/provider/bootstrap/schema_bootstrap.go new file mode 100644 index 00000000..40a9f601 --- /dev/null +++ b/internal/provider/bootstrap/schema_bootstrap.go @@ -0,0 +1,43 @@ +package bootstrap + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/harvester/terraform-provider-harvester/pkg/constants" +) + +func Schema() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + constants.FieldBootstrapInitialPassword: { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ForceNew: true, + Default: "admin", + ValidateFunc: validation.NoZeroValues, + Description: "Default password in the harvester", + }, + constants.FieldBootstrapPassword: { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + Description: "New password for admin user", + }, + constants.FieldBootstrapKubeConfig: { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "~/.kube/config", + ValidateFunc: validation.NoZeroValues, + Description: "Path to store the kubeconfig file", + }, + constants.FieldShouldUpdatePassword: { + Type: schema.TypeBool, + Computed: true, + }, + } + return s +} diff --git a/internal/provider/clusternetwork/datasource_clusternetwork.go b/internal/provider/clusternetwork/datasource_clusternetwork.go index 1340ab24..792fc6b8 100644 --- a/internal/provider/clusternetwork/datasource_clusternetwork.go +++ b/internal/provider/clusternetwork/datasource_clusternetwork.go @@ -8,8 +8,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/importer" ) @@ -22,7 +22,10 @@ func DataSourceClusterNetwork() *schema.Resource { } func dataSourceClusterNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } name := d.Get(constants.FieldCommonName).(string) clusterNetwork, err := c.HarvesterNetworkClient.NetworkV1beta1().ClusterNetworks().Get(ctx, name, metav1.GetOptions{}) if err != nil { diff --git a/internal/provider/clusternetwork/resource_clusternetwork.go b/internal/provider/clusternetwork/resource_clusternetwork.go index d23d1f6a..1bcba4b6 100644 --- a/internal/provider/clusternetwork/resource_clusternetwork.go +++ b/internal/provider/clusternetwork/resource_clusternetwork.go @@ -11,8 +11,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -39,7 +39,10 @@ func ResourceClusterNetwork() *schema.Resource { } func resourceClusterNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } name := d.Get(constants.FieldCommonName).(string) if name == constants.ManagementClusterNetworkName { return diag.FromErr(fmt.Errorf("can not create the existing %s clusternetwork, to avoid this error and continue with the plan, use `terraform import harvester_clusternetwork.%s %s` to import it first", name, name, name)) @@ -57,7 +60,10 @@ func resourceClusterNetworkCreate(ctx context.Context, d *schema.ResourceData, m } func resourceClusterNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -82,7 +88,10 @@ func resourceClusterNetworkUpdate(ctx context.Context, d *schema.ResourceData, m } func resourceClusterNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -99,7 +108,10 @@ func resourceClusterNetworkRead(ctx context.Context, d *schema.ResourceData, met } func resourceClusterNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/provider/image/datasource_image.go b/internal/provider/image/datasource_image.go index 7a30c2a7..f93c7840 100644 --- a/internal/provider/image/datasource_image.go +++ b/internal/provider/image/datasource_image.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) @@ -20,7 +20,10 @@ func DataSourceImage() *schema.Resource { } func dataSourceImageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) displayName := d.Get(constants.FieldImageDisplayName).(string) diff --git a/internal/provider/image/resource_image.go b/internal/provider/image/resource_image.go index 0e55d6b6..e0058da9 100644 --- a/internal/provider/image/resource_image.go +++ b/internal/provider/image/resource_image.go @@ -12,8 +12,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -40,7 +40,10 @@ func ResourceImage() *schema.Resource { } func resourceImageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) toCreate, err := util.ResourceConstruct(d, Creator(namespace, name)) @@ -56,7 +59,10 @@ func resourceImageCreate(ctx context.Context, d *schema.ResourceData, meta inter } func resourceImageUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -81,7 +87,10 @@ func resourceImageUpdate(ctx context.Context, d *schema.ResourceData, meta inter } func resourceImageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -98,7 +107,10 @@ func resourceImageRead(ctx context.Context, d *schema.ResourceData, meta interfa } func resourceImageDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -148,7 +160,10 @@ func resourceImageWaitForState(ctx context.Context, d *schema.ResourceData, meta func resourceImageRefresh(ctx context.Context, d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { return func() (interface{}, string, error) { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return nil, "", err + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) obj, err := c.HarvesterClient.HarvesterhciV1beta1().VirtualMachineImages(namespace).Get(ctx, name, metav1.GetOptions{}) diff --git a/internal/provider/keypair/datasource_keypair.go b/internal/provider/keypair/datasource_keypair.go index a1b3feff..815e74db 100644 --- a/internal/provider/keypair/datasource_keypair.go +++ b/internal/provider/keypair/datasource_keypair.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) @@ -19,7 +19,10 @@ func DataSourceKeypair() *schema.Resource { } func dataSourceKeypairRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) keyPair, err := c.HarvesterClient.HarvesterhciV1beta1().KeyPairs(namespace).Get(ctx, name, metav1.GetOptions{}) diff --git a/internal/provider/keypair/resource_keypair.go b/internal/provider/keypair/resource_keypair.go index 6225de6c..a9d14a18 100644 --- a/internal/provider/keypair/resource_keypair.go +++ b/internal/provider/keypair/resource_keypair.go @@ -10,8 +10,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -38,7 +38,10 @@ func ResourceKeypair() *schema.Resource { } func resourceKeypairCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) toCreate, err := util.ResourceConstruct(d, Creator(namespace, name)) @@ -54,7 +57,10 @@ func resourceKeypairCreate(ctx context.Context, d *schema.ResourceData, meta int } func resourceKeypairUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -80,7 +86,10 @@ func resourceKeypairUpdate(ctx context.Context, d *schema.ResourceData, meta int } func resourceKeypairRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -97,7 +106,10 @@ func resourceKeypairRead(ctx context.Context, d *schema.ResourceData, meta inter } func resourceKeypairDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/provider/network/datasource_network.go b/internal/provider/network/datasource_network.go index 258e06ed..5c805d75 100644 --- a/internal/provider/network/datasource_network.go +++ b/internal/provider/network/datasource_network.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) @@ -19,7 +19,10 @@ func DataSourceNetwork() *schema.Resource { } func dataSourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) obj, err := c.HarvesterClient.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(ctx, name, metav1.GetOptions{}) diff --git a/internal/provider/network/resource_network.go b/internal/provider/network/resource_network.go index b7834e30..9a4d0fcb 100644 --- a/internal/provider/network/resource_network.go +++ b/internal/provider/network/resource_network.go @@ -11,8 +11,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -39,7 +39,10 @@ func ResourceNetwork() *schema.Resource { } func resourceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) toCreate, err := util.ResourceConstruct(d, Creator(c, ctx, namespace, name)) @@ -54,7 +57,10 @@ func resourceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta int } func resourceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -79,7 +85,10 @@ func resourceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta int } func resourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -97,7 +106,10 @@ func resourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta inter } func resourceNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0206e983..961f1600 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -3,13 +3,14 @@ package provider import ( "context" "fmt" - "strings" + "net/url" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/mitchellh/go-homedir" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" + "github.com/harvester/terraform-provider-harvester/internal/provider/bootstrap" "github.com/harvester/terraform-provider-harvester/internal/provider/cloudinitsecret" "github.com/harvester/terraform-provider-harvester/internal/provider/clusternetwork" "github.com/harvester/terraform-provider-harvester/internal/provider/image" @@ -19,17 +20,27 @@ import ( "github.com/harvester/terraform-provider-harvester/internal/provider/virtualmachine" "github.com/harvester/terraform-provider-harvester/internal/provider/vlanconfig" "github.com/harvester/terraform-provider-harvester/internal/provider/volume" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) func Provider() *schema.Provider { p := &schema.Provider{ Schema: map[string]*schema.Schema{ - constants.FieldProviderKubeConfig: { + constants.FieldProviderBootstrap: { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "bootstrap harvester server, it will write content to kubeconfig file", + }, + constants.FieldProviderAPIUrl: { Type: schema.TypeString, Optional: true, Default: "", + Description: "harvester server api url", + }, + constants.FieldProviderKubeConfig: { + Type: schema.TypeString, + Optional: true, Description: "kubeconfig file path, users can use the KUBECONFIG environment variable instead", }, constants.FieldProviderKubeContext: { @@ -60,6 +71,7 @@ func Provider() *schema.Provider { constants.ResourceTypeStorageClass: storageclass.ResourceStorageClass(), constants.ResourceTypeVLANConfig: vlanconfig.ResourceVLANConfig(), constants.ResourceTypeCloudInitSecret: cloudinitsecret.ResourceCloudInitSecret(), + constants.ResourceTypeBootstrap: bootstrap.ResourceBootstrap(), }, ConfigureContextFunc: providerConfig, } @@ -67,29 +79,41 @@ func Provider() *schema.Provider { } func providerConfig(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { + bootstrap := d.Get(constants.FieldProviderBootstrap).(bool) + apiURL := d.Get(constants.FieldProviderAPIUrl).(string) + kubeConfig := d.Get(constants.FieldProviderKubeConfig).(string) kubeContext := d.Get(constants.FieldProviderKubeContext).(string) - kubeConfig, err := homedir.Expand(d.Get(constants.FieldProviderKubeConfig).(string)) - if err != nil { - return nil, diag.FromErr(err) - } + if bootstrap { + if apiURL == "" { + return nil, diag.FromErr(fmt.Errorf("api url is required when bootstrap is true")) + } - c, err := client.NewClient(kubeConfig, kubeContext) - if err != nil { - return nil, diag.FromErr(err) + u, err := url.Parse(apiURL) + if err != nil { + return nil, diag.FromErr(err) + } + + if kubeConfig != "" { + return nil, diag.Errorf("kubeconfig is not allowed when bootstrap is true") + } + + if kubeContext != "" { + return nil, diag.Errorf("kubecontext is not allowed when bootstrap is true") + } + + return &config.Config{ + Bootstrap: bootstrap, + APIURL: u.String(), + }, nil } - // check harvester version from settings - serverVersion, err := c.HarvesterClient. - HarvesterhciV1beta1(). - Settings(). - Get(ctx, "server-version", metav1.GetOptions{}) + kubeConfig, err := homedir.Expand(d.Get(constants.FieldProviderKubeConfig).(string)) if err != nil { return nil, diag.FromErr(err) } - // harvester version v1.0-head, v1.0.2, v1.0.3 is not supported - if strings.HasPrefix(serverVersion.Value, "v1.0") { - return nil, diag.FromErr(fmt.Errorf("current Harvester server version is %s, the minimum supported version is v1.1.0", serverVersion.Value)) - } - return c, nil + return &config.Config{ + KubeConfig: kubeConfig, + KubeContext: kubeContext, + }, nil } diff --git a/internal/provider/storageclass/datasource_storageclass.go b/internal/provider/storageclass/datasource_storageclass.go index c7572db8..6b660839 100644 --- a/internal/provider/storageclass/datasource_storageclass.go +++ b/internal/provider/storageclass/datasource_storageclass.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) @@ -19,7 +19,10 @@ func DataSourceStorageClass() *schema.Resource { } func dataSourceStorageClassRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } name := d.Get(constants.FieldCommonName).(string) storageClass, err := c.StorageClassClient.StorageClasses().Get(ctx, name, metav1.GetOptions{}) diff --git a/internal/provider/storageclass/resource_storageclass.go b/internal/provider/storageclass/resource_storageclass.go index 1b45a19c..749e068d 100644 --- a/internal/provider/storageclass/resource_storageclass.go +++ b/internal/provider/storageclass/resource_storageclass.go @@ -10,8 +10,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -38,7 +38,10 @@ func ResourceStorageClass() *schema.Resource { } func resourceStorageClassCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } name := d.Get(constants.FieldCommonName).(string) toCreate, err := util.ResourceConstruct(d, Creator(name)) if err != nil { @@ -53,7 +56,10 @@ func resourceStorageClassCreate(ctx context.Context, d *schema.ResourceData, met } func resourceStorageClassUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -78,7 +84,10 @@ func resourceStorageClassUpdate(ctx context.Context, d *schema.ResourceData, met } func resourceStorageClassRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -95,7 +104,10 @@ func resourceStorageClassRead(ctx context.Context, d *schema.ResourceData, meta } func resourceStorageClassDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/provider/virtualmachine/datasource_virtualmachine.go b/internal/provider/virtualmachine/datasource_virtualmachine.go index 680b67f9..d74d0acf 100644 --- a/internal/provider/virtualmachine/datasource_virtualmachine.go +++ b/internal/provider/virtualmachine/datasource_virtualmachine.go @@ -8,7 +8,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) @@ -20,7 +20,10 @@ func DataSourceVirtualMachine() *schema.Resource { } func dataSourceVirtualMachineRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) vm, err := c.HarvesterClient.KubevirtV1().VirtualMachines(namespace).Get(ctx, name, metav1.GetOptions{}) diff --git a/internal/provider/virtualmachine/resource_virtualmachine.go b/internal/provider/virtualmachine/resource_virtualmachine.go index 5020d5ae..e2d7cc3d 100644 --- a/internal/provider/virtualmachine/resource_virtualmachine.go +++ b/internal/provider/virtualmachine/resource_virtualmachine.go @@ -14,8 +14,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubevirtv1 "kubevirt.io/api/core/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -42,7 +42,10 @@ func ResourceVirtualMachine() *schema.Resource { } func resourceVirtualMachineCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) toCreate, err := util.ResourceConstruct(d, Creator(c, ctx, namespace, name)) @@ -65,7 +68,10 @@ func resourceVirtualMachineCreate(ctx context.Context, d *schema.ResourceData, m } func resourceVirtualMachineUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -110,7 +116,10 @@ func resourceVirtualMachineUpdate(ctx context.Context, d *schema.ResourceData, m } func resourceVirtualMachineRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -134,7 +143,10 @@ func resourceVirtualMachineRead(ctx context.Context, d *schema.ResourceData, met } func resourceVirtualMachineDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -147,24 +159,8 @@ func resourceVirtualMachineDelete(ctx context.Context, d *schema.ResourceData, m } return diag.FromErr(err) } - deleteConfigs := make(map[string]bool) - if diskList, ok := d.GetOk(constants.FieldVirtualMachineDisk); ok { - for _, disk := range diskList.([]interface{}) { - r := disk.(map[string]interface{}) - diskName := r[constants.FieldDiskName].(string) - deleteConfigs[diskName] = r[constants.FieldDiskAutoDelete].(bool) - } - } - removedPVCs := make([]string, 0, len(vm.Spec.Template.Spec.Volumes)) - for _, volume := range vm.Spec.Template.Spec.Volumes { - if volume.PersistentVolumeClaim == nil { - continue - } - if autoDelete, ok := deleteConfigs[volume.Name]; ok && !autoDelete { - continue - } - removedPVCs = append(removedPVCs, volume.PersistentVolumeClaim.ClaimName) - } + + removedPVCs := getRemovedPVCs(d, vm) vmCopy := vm.DeepCopy() vmCopy.Annotations[harvesterutil.RemovedPVCsAnnotationKey] = strings.Join(removedPVCs, ",") _, err = c.HarvesterClient.KubevirtV1().VirtualMachines(namespace).Update(ctx, vmCopy, metav1.UpdateOptions{}) @@ -229,7 +225,10 @@ func resourceVirtualMachineWaitForState(ctx context.Context, d *schema.ResourceD func resourceVirtualMachineRefresh(ctx context.Context, d *schema.ResourceData, meta interface{}, namespace, name, oldInstanceUID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return nil, "", err + } vm, err := c.HarvesterClient.KubevirtV1().VirtualMachines(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { @@ -268,3 +267,25 @@ func IsNeedRestart(d *schema.ResourceData, runStrategy kubevirtv1.VirtualMachine } return false } + +func getRemovedPVCs(d *schema.ResourceData, vm *kubevirtv1.VirtualMachine) []string { + deleteConfigs := make(map[string]bool) + if diskList, ok := d.GetOk(constants.FieldVirtualMachineDisk); ok { + for _, disk := range diskList.([]interface{}) { + r := disk.(map[string]interface{}) + diskName := r[constants.FieldDiskName].(string) + deleteConfigs[diskName] = r[constants.FieldDiskAutoDelete].(bool) + } + } + removedPVCs := make([]string, 0, len(vm.Spec.Template.Spec.Volumes)) + for _, volume := range vm.Spec.Template.Spec.Volumes { + if volume.PersistentVolumeClaim == nil { + continue + } + if autoDelete, ok := deleteConfigs[volume.Name]; ok && !autoDelete { + continue + } + removedPVCs = append(removedPVCs, volume.PersistentVolumeClaim.ClaimName) + } + return removedPVCs +} diff --git a/internal/provider/vlanconfig/datasource_vlanconfig.go b/internal/provider/vlanconfig/datasource_vlanconfig.go index 80115bb0..bc85678e 100644 --- a/internal/provider/vlanconfig/datasource_vlanconfig.go +++ b/internal/provider/vlanconfig/datasource_vlanconfig.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) @@ -19,7 +19,10 @@ func DataSourceVLANConfig() *schema.Resource { } func dataSourceVLANConfigRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } name := d.Get(constants.FieldCommonName).(string) vlanConfig, err := c.HarvesterNetworkClient.NetworkV1beta1().VlanConfigs().Get(ctx, name, metav1.GetOptions{}) diff --git a/internal/provider/vlanconfig/resource_vlanconfig.go b/internal/provider/vlanconfig/resource_vlanconfig.go index a774a0e7..a7832317 100644 --- a/internal/provider/vlanconfig/resource_vlanconfig.go +++ b/internal/provider/vlanconfig/resource_vlanconfig.go @@ -11,8 +11,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -39,7 +39,10 @@ func ResourceVLANConfig() *schema.Resource { } func resourceVLANConfigCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } name := d.Get(constants.FieldCommonName).(string) toCreate, err := util.ResourceConstruct(d, Creator(name)) if err != nil { @@ -54,7 +57,10 @@ func resourceVLANConfigCreate(ctx context.Context, d *schema.ResourceData, meta } func resourceVLANConfigUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -79,7 +85,10 @@ func resourceVLANConfigUpdate(ctx context.Context, d *schema.ResourceData, meta } func resourceVLANConfigRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -96,7 +105,10 @@ func resourceVLANConfigRead(ctx context.Context, d *schema.ResourceData, meta in } func resourceVLANConfigDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } _, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/provider/volume/datasource_volume.go b/internal/provider/volume/datasource_volume.go index 9e360423..e10a72c7 100644 --- a/internal/provider/volume/datasource_volume.go +++ b/internal/provider/volume/datasource_volume.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/pkg/constants" ) @@ -19,7 +19,10 @@ func DataSourceVolume() *schema.Resource { } func dataSourceVolumeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) obj, err := c.KubeClient.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, metav1.GetOptions{}) diff --git a/internal/provider/volume/resource_volume.go b/internal/provider/volume/resource_volume.go index 7d9d8ef2..fd7e8f86 100644 --- a/internal/provider/volume/resource_volume.go +++ b/internal/provider/volume/resource_volume.go @@ -10,8 +10,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/config" "github.com/harvester/terraform-provider-harvester/internal/util" - "github.com/harvester/terraform-provider-harvester/pkg/client" "github.com/harvester/terraform-provider-harvester/pkg/constants" "github.com/harvester/terraform-provider-harvester/pkg/helper" "github.com/harvester/terraform-provider-harvester/pkg/importer" @@ -38,7 +38,10 @@ func ResourceVolume() *schema.Resource { } func resourceVolumeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace := d.Get(constants.FieldCommonNamespace).(string) name := d.Get(constants.FieldCommonName).(string) toCreate, err := util.ResourceConstruct(d, Creator(namespace, name)) @@ -53,7 +56,10 @@ func resourceVolumeCreate(ctx context.Context, d *schema.ResourceData, meta inte } func resourceVolumeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -78,7 +84,10 @@ func resourceVolumeUpdate(ctx context.Context, d *schema.ResourceData, meta inte } func resourceVolumeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) @@ -95,7 +104,10 @@ func resourceVolumeRead(ctx context.Context, d *schema.ResourceData, meta interf } func resourceVolumeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - c := meta.(*client.Client) + c, err := meta.(*config.Config).K8sClient() + if err != nil { + return diag.FromErr(err) + } namespace, name, err := helper.IDParts(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/util/http.go b/internal/util/http.go new file mode 100644 index 00000000..ffcaf295 --- /dev/null +++ b/internal/util/http.go @@ -0,0 +1,129 @@ +package util + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "time" +) + +const ( + maxHTTPRedirect = 5 +) + +func DoPost(url, data, cacert string, insecure bool, headers map[string]string) (*http.Response, error) { + if url == "" { + return nil, fmt.Errorf("doing post: URL is nil") + } + + jsonBytes := []byte(data) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes)) + if err != nil { + return nil, err + } + + for k, v := range headers { + req.Header.Set(k, v) + } + + client := &http.Client{} + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, //nolint:gosec + Proxy: http.ProxyFromEnvironment, + } + + if cacert != "" { + // Get the SystemCertPool, continue with an empty pool on error + rootCAs, _ := x509.SystemCertPool() + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + + // Append our cert to the system pool + if ok := rootCAs.AppendCertsFromPEM([]byte(cacert)); !ok { + log.Println("No certs appended, using system certs only") + } + transport.TLSClientConfig.RootCAs = rootCAs + } + + client.Transport = transport + + return client.Do(req) +} + +func GetJSONBody(resp *http.Response) (map[string]interface{}, error) { + response := make(map[string]interface{}) + body, _ := io.ReadAll(resp.Body) + err := json.Unmarshal(body, &response) + if err != nil { + return response, err + } + return response, nil +} + +func DoGet(url, username, password, token, cacert string, insecure bool) (*http.Response, error) { + start := time.Now() + + if url == "" { + return nil, fmt.Errorf("doing get: URL is nil") + } + log.Println("Getting from ", url) + + client := &http.Client{ + Timeout: time.Duration(60 * time.Second), + CheckRedirect: func(req *http.Request, via []*http.Request) error { + if len(via) >= maxHTTPRedirect { + return fmt.Errorf("stopped after %d redirects", maxHTTPRedirect) + } + if len(token) > 0 { + req.Header.Add("Authorization", "Bearer "+token) + } else if len(username) > 0 && len(password) > 0 { + s := username + ":" + password + req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(s))) + } + return nil + }, + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, //nolint:gosec + Proxy: http.ProxyFromEnvironment, + } + + if cacert != "" { + // Get the SystemCertPool, continue with an empty pool on error + rootCAs, _ := x509.SystemCertPool() + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + + // Append our cert to the system pool + if ok := rootCAs.AppendCertsFromPEM([]byte(cacert)); !ok { + log.Println("No certs appended, using system certs only") + } + transport.TLSClientConfig.RootCAs = rootCAs + } + client.Transport = transport + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("doing get: %v", err) + } + if len(token) > 0 { + req.Header.Add("Authorization", "Bearer "+token) + } else if len(username) > 0 && len(password) > 0 { + s := username + ":" + password + req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(s))) + } + // Timings recorded as part of internal metrics + log.Println("Time to get req: ", float64((time.Since(start))/time.Millisecond), " ms") + + return client.Do(req) +} diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 714c555c..7a2079aa 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -4,6 +4,8 @@ const ( NamespaceDefault = "default" NamespaceHarvesterSystem = "harvester-system" + FieldProviderBootstrap = "bootstrap" + FieldProviderAPIUrl = "api_url" FieldProviderKubeConfig = "kubeconfig" FieldProviderKubeContext = "kubecontext" diff --git a/pkg/constants/constants_bootstrap.go b/pkg/constants/constants_bootstrap.go new file mode 100644 index 00000000..70254d7b --- /dev/null +++ b/pkg/constants/constants_bootstrap.go @@ -0,0 +1,10 @@ +package constants + +const ( + ResourceTypeBootstrap = "harvester_bootstrap" + + FieldBootstrapInitialPassword = "initial_password" + FieldBootstrapPassword = "password" + FieldBootstrapKubeConfig = "kubeconfig" + FieldShouldUpdatePassword = "should_update_password" +)