Skip to content
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

Add bootstrap #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -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
}
193 changes: 193 additions & 0 deletions internal/provider/bootstrap/resource_bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
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/mitchellh/go-homedir"

"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"))
}

kubeConfig, err := homedir.Expand(d.Get(constants.FieldProviderKubeConfig).(string))
if err != nil {
return diag.FromErr(err)
}

// 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) {
initialPassword := d.Get(constants.FieldBootstrapInitialPassword).(string)
password := d.Get(constants.FieldBootstrapPassword).(string)
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(kubeConfig, []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"))
}

kubeConfig, err := homedir.Expand(d.Get(constants.FieldProviderKubeConfig).(string))
if err != nil {
return diag.FromErr(err)
}

// 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(kubeConfig, []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 {
err = d.Set(constants.FieldShouldUpdatePassword, true)
return tokenID, token, err
}

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 {
err = d.Set(constants.FieldShouldUpdatePassword, false)
return tokenID, token, err
}
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
}
43 changes: 43 additions & 0 deletions internal/provider/bootstrap/schema_bootstrap.go
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 5 additions & 2 deletions internal/provider/clusternetwork/datasource_clusternetwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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 {
Expand Down
22 changes: 17 additions & 5 deletions internal/provider/clusternetwork/resource_clusternetwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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))
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions internal/provider/image/datasource_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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)
Expand Down
Loading