-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: PoAn Yang <[email protected]>
- Loading branch information
1 parent
45fde4b
commit 951a694
Showing
23 changed files
with
655 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
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 | ||
log.Printf("Doing login") | ||
tokenID, token, err := doUserLogin(c.APIURL, bootstrapDefaultUser, initialPassword, bootstrapDefaultTTL, bootstrapDefaultSessionDesc, "", true) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
d.SetId(tokenID) | ||
|
||
// change password | ||
log.Printf("Doing change password") | ||
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")) | ||
} | ||
|
||
initialPassword := d.Get(constants.FieldBootstrapInitialPassword).(string) | ||
password := d.Get(constants.FieldBootstrapPassword).(string) | ||
|
||
// If fails, try to login with default admin user, current password and initial password if fails | ||
loginPass := []string{ | ||
initialPassword, | ||
password, | ||
} | ||
|
||
// login to get token | ||
log.Printf("Doing login") | ||
var ( | ||
tokenID, token string | ||
err error | ||
) | ||
|
||
for _, pass := range loginPass { | ||
tokenID, token, err = doUserLogin(c.APIURL, bootstrapDefaultUser, pass, bootstrapDefaultTTL, bootstrapDefaultSessionDesc, "", true) | ||
if err == nil { | ||
break | ||
} | ||
} | ||
if tokenID == "" { | ||
log.Printf("[INFO] Bootstrap is unable to login to Harvester") | ||
d.SetId("") | ||
return nil | ||
} | ||
|
||
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 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
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", | ||
}, | ||
} | ||
return s | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.