diff --git a/rundeck/provider.go b/rundeck/provider.go index cf530be58..d6b3313bf 100644 --- a/rundeck/provider.go +++ b/rundeck/provider.go @@ -28,10 +28,22 @@ func Provider() terraform.ResourceProvider { }, "auth_token": { Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: schema.EnvDefaultFunc("RUNDECK_AUTH_TOKEN", nil), Description: "Auth token to use with the Rundeck API.", }, + "auth_username": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("RUNDECK_AUTH_USERNAME", nil), + Description: "Username used to request a tiken for the Rundeck API.", + }, + "auth_password": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("RUNDECK_AUTH_PASSWORD", nil), + Description: "Password used to request a tiken for the Rundeck API.", + }, }, ResourcesMap: map[string]*schema.Resource{ @@ -58,7 +70,23 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return nil, error } - token := d.Get("auth_token").(string) + _, okToken := d.GetOk("auth_token") + _, okUsername := d.GetOk("auth_username") + _, okPassword := d.GetOk("auth_password") + + var token string + + if okToken { + token = d.Get("auth_token").(string) + } else if okUsername && okPassword { + t, err := getToken(d) + token = t + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("auth_token need to be set of auth_username and auth_password") + } client := rundeck.NewRundeckWithBaseURI(apiURL.String()) client.Authorizer = &auth.TokenAuthorizer{Token: token} diff --git a/rundeck/provider_test.go b/rundeck/provider_test.go index d66fe0d53..e529c6345 100644 --- a/rundeck/provider_test.go +++ b/rundeck/provider_test.go @@ -92,7 +92,15 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("RUNDECK_URL"); v == "" { t.Fatal("RUNDECK_URL must be set for acceptance tests") } - if v := os.Getenv("RUNDECK_AUTH_TOKEN"); v == "" { - t.Fatal("RUNDECK_AUTH_TOKEN must be set for acceptance tests") + + token := os.Getenv("RUNDECK_AUTH_TOKEN") + username := os.Getenv("RUNDECK_AUTH_USERNAME") + password := os.Getenv("RUNDECK_AUTH_PASSWORD") + if !(token != "" || (username != "" && password != "")) { + t.Logf("RUNDECK_AUTH_TOKEN=%s", token) + t.Logf("RUNDECK_AUTH_USERNAME=%s", username) + t.Logf("RUNDECK_AUTH_PASSWORD=%s", password) + t.Fatal("RUNDECK_AUTH_TOKEN must be set for acceptance tests or RUNDECK_AUTH_USERNAME and RUNDECK_AUTH_PASSWORD") } + } diff --git a/rundeck/token.go b/rundeck/token.go new file mode 100644 index 000000000..012d9c635 --- /dev/null +++ b/rundeck/token.go @@ -0,0 +1,113 @@ +package rundeck + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/url" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +type TokenResp struct { + User string `json:"user"` + Token string `json:"token"` + ID string `json:"id"` + Creator string `json:"creator"` + Name string `json:"name"` + Expiration time.Time `json:"expiration"` + Roles []string `json:"roles"` + Expired bool `json:"expired"` +} + +func getToken(d *schema.ResourceData) (string, error) { + urlP, _ := d.Get("url").(string) + apiVersion, _ := d.Get("api_version").(string) + username, _ := d.Get("auth_username").(string) + password, _ := d.Get("auth_password").(string) + + return _getToken(urlP, apiVersion, username, password) + +} + +func _getToken(urlP string, apiVersion string, username string, password string) (string, error) { + + secCheckUrlString := fmt.Sprintf("%s/j_security_check", urlP) + secCheckUrl, err := url.Parse(secCheckUrlString) + + if err != nil { + return "", err + } + + jar, err := cookiejar.New(nil) + if err != nil { + return "", err + } + + client := &http.Client{ + Jar: jar, + } + + data := url.Values{ + "j_username": {username}, + "j_password": {password}, + } + _, err = client.PostForm(secCheckUrl.String(), data) + if err != nil { + return "", err + } + + tokenUrlString := fmt.Sprintf("%s/api/%s/tokens", urlP, "43") + tokenUrl, err := url.Parse(tokenUrlString) + + if err != nil { + return "", err + } + + tokenBody := map[string]interface{}{} + tokenBody["user"] = username + tokenBody["roles"] = []string{"*"} + tokenBody["duration"] = "0" + tokenBody["name"] = "terraform-token" + tokenBodyJson, err := json.Marshal(tokenBody) + if err != nil { + return "", err + } + + req, err := http.NewRequest("POST", tokenUrl.String(), bytes.NewBuffer(tokenBodyJson)) + if err != nil { + return "", err + } + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + + resp, err := client.Do(req) + if resp.StatusCode < 200 || resp.StatusCode > 299 { + body, _ := ioutil.ReadAll(resp.Body) + return "", fmt.Errorf("statuscode %d\n%s", resp.StatusCode, string(body)) + } else if err != nil { + return "", err + } + + tokenResp := &TokenResp{} + err = json.NewDecoder(resp.Body).Decode(tokenResp) + if err != nil { + return "", err + } + + if version, _ := strconv.Atoi(apiVersion); version >= 19 { + return tokenResp.Token, nil + } else if tokenResp.Token != "" { + return tokenResp.Token, nil + } else if tokenResp.ID != "" { + return tokenResp.ID, nil + } + + return tokenResp.Token, nil + +} diff --git a/rundeck/token_test.go b/rundeck/token_test.go new file mode 100644 index 000000000..9d35b51d2 --- /dev/null +++ b/rundeck/token_test.go @@ -0,0 +1,27 @@ +package rundeck + +import ( + "os" + "testing" +) + +func TestAccToken(t *testing.T) { + testAccPreCheck(t) + + username := os.Getenv("RUNDECK_AUTH_USERNAME") + password := os.Getenv("RUNDECK_AUTH_PASSWORD") + apiVersion := os.Getenv("RUNDECK_API_VERSION") + if apiVersion == "" { + apiVersion = "14" + } + url := os.Getenv("RUNDECK_URL") + + if username != "" && password != "" { + _, err := _getToken(url, apiVersion, username, password) + if err != nil { + t.Fatalf("failed to get a token: %s", err) + } + } else { + t.Logf("Can not run TestAccToken test as there is no username and password defined") + } +} diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 0881a473f..827a5b197 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -22,8 +22,16 @@ The provider configuration block accepts the following arguments: minium supported version. May alternatively be set via the ``RUNDECK_API_VERSION`` environment variable. -* ``auth_token`` - (Required) The API auth token to use when making requests. May alternatively - be set via the ``RUNDECK_AUTH_TOKEN`` environment variable. +* ``auth_token`` - The API auth token to use when making requests. May alternatively + be set via the ``RUNDECK_AUTH_TOKEN`` environment variable. (RECOMMENDED) + +**OR** + +* ``auth_username`` - Local Login User Name. +* ``auth_password`` - Local Login Passwrod. + +> Note: Username and Password auth will not work with SSO configured systems. It relies on local Rundeck accounts. Please be sensitive to keeping passwords in your plan files! + Use the navigation to the left to read about the available resources.