diff --git a/docs/resources/sonarqube_settings.md b/docs/resources/sonarqube_settings.md index 6177428f..6e932327 100644 --- a/docs/resources/sonarqube_settings.md +++ b/docs/resources/sonarqube_settings.md @@ -15,7 +15,7 @@ resource "sonarqube_setting" "single_setting" { ## Example: create a setting with multiple values ```terraform resource "sonarqube_setting" "multi_value_setting" { - key = "sonar.global.exclusions" + key = "sonar.global.exclusions" values = ["foo", "bar/**/*.*"] } ``` diff --git a/sonarqube/resource_sonarqube_portfolio_test.go b/sonarqube/resource_sonarqube_portfolio_test.go index 3875d5d7..d17a0a94 100644 --- a/sonarqube/resource_sonarqube_portfolio_test.go +++ b/sonarqube/resource_sonarqube_portfolio_test.go @@ -604,7 +604,7 @@ func TestAccSonarqubePortfolioManualImport(t *testing.T) { ResourceName: name, ImportState: true, ImportStateVerify: true, - Check: checks, + Check: checks, }, }, }) diff --git a/sonarqube/resource_sonarqube_project.go b/sonarqube/resource_sonarqube_project.go index eee2f562..788a390f 100644 --- a/sonarqube/resource_sonarqube_project.go +++ b/sonarqube/resource_sonarqube_project.go @@ -224,18 +224,22 @@ func resourceSonarqubeProjectRead(d *schema.ResourceData, m interface{}) error { // Get settings var projectSettings []Setting if _, ok := d.GetOk("setting"); ok { + componentSettings := d.Get("setting").([]interface{}) projectSettings, err = getComponentSettings(d.Id(), m) if err != nil { return fmt.Errorf("resourceSonarqubeProjectRead: Failed to read project settings: %+v", err) } - if len(projectSettings) > 0 { - settings := make([]interface{}, len(projectSettings)) - for i, s := range projectSettings { - settings[i] = s.ToMap() + settings := make([]interface{}, len(componentSettings)) + for i, s := range componentSettings { + key := s.(map[string]interface{})["key"].(string) + for _, apiSetting := range projectSettings { + if key == apiSetting.Key { + settings[i] = apiSetting.ToMap() + } } - d.Set("setting", settings) } + d.Set("setting", settings) } if len(projectReadResponse.Component.Tags) > 0 { diff --git a/sonarqube/resource_sonarqube_project_test.go b/sonarqube/resource_sonarqube_project_test.go index 495c7ad3..b7845a00 100644 --- a/sonarqube/resource_sonarqube_project_test.go +++ b/sonarqube/resource_sonarqube_project_test.go @@ -38,12 +38,12 @@ func testAccSonarqubeProjectTagsConfig(rnd string, name string, project string, name = "%[2]s" project = "%[3]s" visibility = "%[4]s" - tags = %[5]s // Note that the "" should be missing since this is a list + tags = %[5]s // Note that the "" should be missing since this is a list } `, rnd, name, project, visibility, formattedTags) } -func testAccSonarqubeProjectSettingsConfig(rnd string, name string, project string, visibility string) string { +func testAccSonarqubeProjectSettingsConfig(rnd string, name string, project string, visibility string, value string) string { return fmt.Sprintf(` resource "sonarqube_project" "%[1]s" { name = "%[2]s" @@ -51,11 +51,11 @@ func testAccSonarqubeProjectSettingsConfig(rnd string, name string, project stri visibility = "%[4]s" setting { - key = "sonar.demo" - value = "sonarqube@example.org" + key = "sonar.docker.activate" + value = "%[5]s" } } - `, rnd, name, project, visibility) + `, rnd, name, project, visibility, value) } func testAccSonarqubeProjectSettingsMultiple(rnd string, key string, name string, values []string, fields map[string]string) string { @@ -68,12 +68,12 @@ func testAccSonarqubeProjectSettingsMultiple(rnd string, key string, name string visibility = "public" setting { - key = "sonar.demo" - value = "sonarqube@example.org" + key = "sonar.terraform.activate" + value = "true" } setting { - key = "sonar.global.exclusions" + key = "sonar.terraform.file.suffixes" values = %[4]s } @@ -272,12 +272,12 @@ func TestAccSonarqubeProjectSettingsCreate(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccSonarqubeProjectSettingsConfig(rnd, "testAccSonarqubeProject", "testAccSonarqubeProject", "public"), + Config: testAccSonarqubeProjectSettingsConfig(rnd, "testAccSonarqubeProject", "testAccSonarqubeProject", "public", "false"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(name, "project", "testAccSonarqubeProject"), resource.TestCheckResourceAttr(name, "setting.#", strconv.Itoa(expectedSettings)), - resource.TestCheckResourceAttr(name, "setting.0.key", "sonar.demo"), - resource.TestCheckResourceAttr(name, "setting.0.value", "sonarqube@example.org"), + resource.TestCheckResourceAttr(name, "setting.0.key", "sonar.docker.activate"), + resource.TestCheckResourceAttr(name, "setting.0.value", "false"), ), }, }, @@ -300,23 +300,32 @@ func TestAccSonarqubeProjectSettingsUpdate(t *testing.T) { ), }, { - Config: testAccSonarqubeProjectSettingsConfig(rnd, "testAccSonarqubeProject", "testAccSonarqubeProject", "public"), + Config: testAccSonarqubeProjectSettingsConfig(rnd, "testAccSonarqubeProject", "testAccSonarqubeProject", "public", "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "project", "testAccSonarqubeProject"), + resource.TestCheckResourceAttr(name, "setting.#", "1"), + resource.TestCheckResourceAttr(name, "setting.0.key", "sonar.docker.activate"), + resource.TestCheckResourceAttr(name, "setting.0.value", "false"), + ), + }, + { + Config: testAccSonarqubeProjectSettingsConfig(rnd, "testAccSonarqubeProject", "testAccSonarqubeProject", "public", "true"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(name, "project", "testAccSonarqubeProject"), resource.TestCheckResourceAttr(name, "setting.#", "1"), - resource.TestCheckResourceAttr(name, "setting.0.key", "sonar.demo"), - resource.TestCheckResourceAttr(name, "setting.0.value", "sonarqube@example.org"), + resource.TestCheckResourceAttr(name, "setting.0.key", "sonar.docker.activate"), + resource.TestCheckResourceAttr(name, "setting.0.value", "true"), ), }, }, }) } -func TestAccSonarqubePortfolioSettingsTypes(t *testing.T) { +func TestAccSonarqubeProjectSettingsTypes(t *testing.T) { rnd := generateRandomResourceName() name := "sonarqube_project." + rnd expectedConditions := 3 - values := []string{"foo", "bar"} + values := []string{".tf", ".tfvars"} fieldValues := map[string]string{"ruleKey": "foo", "resourceKey": "bar"} resource.Test(t, resource.TestCase{ @@ -328,11 +337,11 @@ func TestAccSonarqubePortfolioSettingsTypes(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(name, "project", "testAccSonarqubeProject"), resource.TestCheckResourceAttr(name, "setting.#", strconv.Itoa(expectedConditions)), - resource.TestCheckResourceAttr(name, "setting.0.key", "sonar.demo"), - resource.TestCheckResourceAttr(name, "setting.0.value", "sonarqube@example.org"), - resource.TestCheckResourceAttr(name, "setting.1.key", "sonar.global.exclusions"), - resource.TestCheckTypeSetElemAttr(name, "setting.1.values.*", "foo"), - resource.TestCheckTypeSetElemAttr(name, "setting.1.values.*", "bar"), + resource.TestCheckResourceAttr(name, "setting.0.key", "sonar.terraform.activate"), + resource.TestCheckResourceAttr(name, "setting.0.value", "true"), + resource.TestCheckResourceAttr(name, "setting.1.key", "sonar.terraform.file.suffixes"), + resource.TestCheckTypeSetElemAttr(name, "setting.1.values.*", ".tf"), + resource.TestCheckTypeSetElemAttr(name, "setting.1.values.*", ".tfvars"), resource.TestCheckResourceAttr(name, "setting.2.key", "sonar.issue.ignore.multicriteria"), resource.TestCheckTypeSetElemNestedAttrs(name, "setting.2.field_values.*", fieldValues), ), diff --git a/sonarqube/resource_sonarqube_setting.go b/sonarqube/resource_sonarqube_setting.go index 7d243cc0..c9125a9f 100644 --- a/sonarqube/resource_sonarqube_setting.go +++ b/sonarqube/resource_sonarqube_setting.go @@ -3,6 +3,7 @@ package sonarqube import ( "encoding/json" "fmt" + "log" "net/http" "net/url" "sort" @@ -27,12 +28,13 @@ type GetSettings struct { func (a Setting) ToMap() map[string]interface{} { obj := make(map[string]interface{}) + obj["key"] = a.Key obj["value"] = a.Value if a.Values != nil { obj["values"] = a.Values } if a.FieldValues != nil { - obj["fieldValues"] = a.FieldValues + obj["field_values"] = a.FieldValues } return obj } @@ -247,12 +249,8 @@ func getComponentSettings(component string, m interface{}) ([]Setting, error) { } settingsList := make([]Setting, 0) - // Filter settings (removing inherited) - for _, e := range settingReadResponse.Setting { - if e.Inherited == false { - settingsList = append(settingsList, e) - } - } + // Filter settings by parameter inherited + settingsList = append(settingsList, settingReadResponse.Setting...) // Make sure the order is always the same for when we are comparing lists of conditions sort.Slice(settingsList, func(i, j int) bool { @@ -266,30 +264,33 @@ func synchronizeSettings(d *schema.ResourceData, m interface{}) (bool, error) { changed := false componentId := d.Id() componentSettings := d.Get("setting").([]interface{}) - apiComponentSettings, _ := getComponentSettings(componentId, m) - // Make sure the order is always the same for when we are comparing lists of conditions - sort.Slice(componentSettings, func(i, j int) bool { - return componentSettings[i].(map[string]interface{})["key"].(string) < componentSettings[j].(map[string]interface{})["key"].(string) - }) - // Determine which conditions have been added or changed and update those for _, s := range componentSettings { setting := s.(map[string]interface{}) key := setting["key"].(string) // Update the condition if it already exists + exists := false for _, apiSetting := range apiComponentSettings { if key == apiSetting.Key { + exists = true if checkSettingDiff(setting, apiSetting) { err := setComponentSetting(componentId, setting, m, &changed) if err != nil { - return false, fmt.Errorf("addOrUpdateCondition: Failed to update setting '%s': %+v", key, err) + return false, fmt.Errorf("synchronizeSettings: Failed to update setting '%s': %+v", key, err) } } } } + // Add the condition because it does not already exist + if !exists { + err := setComponentSetting(componentId, setting, m, &changed) + if err != nil { + return false, fmt.Errorf("synchronizeSettings: Failed to create setting '%s': %+v", key, err) + } + } } // Determine if any settings have been removed and delete them @@ -306,21 +307,7 @@ func synchronizeSettings(d *schema.ResourceData, m interface{}) (bool, error) { } func checkSettingDiff(a map[string]interface{}, b Setting) bool { - if a["value"] != nil { - return a["value"].(string) != b.Value - } else if a["values"] != nil { - // array of strings - values := a["field_values"].([]string) - if len(values) != len(b.FieldValues) { - return false - } - for i := range values { - if string(values[i]) != string(b.Values[i]) { - return false - } - } - return true - } else if a["field_values"] != nil { + if a["field_values"] != nil { // array of objects of key/value pairs fieldValues := a["field_values"].([]interface{}) if len(fieldValues) != len(b.FieldValues) { @@ -334,6 +321,20 @@ func checkSettingDiff(a map[string]interface{}, b Setting) bool { } } return true + } else if a["values"] != nil && len(a["values"].([]string)) > 0 { + // array of strings + values := a["values"].([]string) + if len(values) != len(b.Values) { + return false + } + for i := range values { + if string(values[i]) != string(b.Values[i]) { + return false + } + } + return true + } else if a["value"] != nil && a["value"] != "" { + return a["value"].(string) != b.Value } return false } @@ -342,20 +343,31 @@ func getComponentSettingUrlEncode(setting map[string]interface{}) url.Values { raw := url.Values{ "key": []string{setting["key"].(string)}, } - if setting["value"] != nil { + addedSetting := false + log.Printf("[DEBUG] setting.value '%s'", setting["value"]) + log.Printf("[DEBUG] setting.values '%s'", setting["values"]) + log.Printf("[DEBUG] setting.field_values '%s'", setting["field_values"]) + if setting["value"] != nil && setting["value"] != "" { raw.Add("value", setting["value"].(string)) - } else if setting["values"] != nil { + addedSetting = true + } + + if setting["values"] != nil && !addedSetting { // array of strings for _, value := range setting["values"].([]interface{}) { raw.Add("values", value.(string)) + addedSetting = true } - } else if setting["field_values"] != nil { + } + + if setting["field_values"] != nil && !addedSetting { // array of objects of key/value pairs fieldValues := setting["field_values"].([]interface{}) for _, value := range fieldValues { b, _ := json.Marshal(value) fv := string(b) raw.Add("fieldValues", fv) + addedSetting = true } } return raw @@ -372,7 +384,7 @@ func setComponentSetting(component string, setting map[string]interface{}, m int m.(*ProviderConfiguration).httpClient, "POST", sonarQubeURL.String(), - http.StatusOK, + http.StatusNoContent, "setComponentSettings", ) if err != nil { @@ -398,7 +410,7 @@ func removeComponentSettings(component string, newSettings []interface{}, apiPro break } } - if !found { + if !found && !apiSetting.Inherited { toDelete = append(toDelete, fmt.Sprint(apiSetting.Key)) } } diff --git a/sonarqube/resource_sonarqube_webhook_test.go b/sonarqube/resource_sonarqube_webhook_test.go index 4401d818..0e4078cd 100644 --- a/sonarqube/resource_sonarqube_webhook_test.go +++ b/sonarqube/resource_sonarqube_webhook_test.go @@ -110,8 +110,8 @@ func TestAccSonarqubeWebhookProjectBasic(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, + ResourceName: resourceName, + ImportState: true, ImportStateIdFunc: testAccSonarqubeWebhookProjectImportID(resourceName), ImportStateVerify: true, ImportStateVerifyIgnore: []string{"secret"},