diff --git a/.golangci.yml b/.golangci.yml index c35ba0e1ed..d20144db0d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -79,6 +79,7 @@ linters: - usetesting # Reports uses of functions with replacement inside the testing package. [auto-fix] - wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false] - whitespace # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true] + - wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false] - zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg` [fast: false, auto-fix: false] disable: @@ -101,7 +102,6 @@ linters: - nestif # Reports deeply nested if statements [fast: true, auto-fix: false] - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false] - varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false] - - wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false] linters-settings: goconst: diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 8e81109887..92b316a3ce 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -26,6 +26,7 @@ type TestTools struct { func NewTestTools(t *testing.T) *TestTools { t.Helper() + ctx := context.Background() folder, err := os.Getwd() @@ -142,6 +143,7 @@ func compareJSONBodies(expected, actual map[string]interface{}) bool { // We do not want to generate new cassettes for each new features continue } + if !compareJSONFields(expectedValue, actual[key]) { return false } diff --git a/internal/acctest/checks.go b/internal/acctest/checks.go index b701d01c57..86461354fa 100644 --- a/internal/acctest/checks.go +++ b/internal/acctest/checks.go @@ -18,13 +18,16 @@ func CheckResourceIDChanged(resourceName string, resourceID *string) resource.Te if resourceID == nil || *resourceID == "" { return errors.New("resourceID was not set") } + rs, ok := s.RootModule().Resources[resourceName] if !ok { return fmt.Errorf("resource was not found: %s", resourceName) } + if *resourceID == rs.Primary.ID { return errors.New("resource ID persisted when it should have changed") } + *resourceID = rs.Primary.ID return nil @@ -39,9 +42,11 @@ func CheckResourceIDPersisted(resourceName string, resourceID *string) resource. if !ok { return fmt.Errorf("resource was not found: %s", resourceName) } + if *resourceID != "" && *resourceID != rs.Primary.ID { return errors.New("resource ID changed when it should have persisted") } + *resourceID = rs.Primary.ID return nil @@ -84,10 +89,12 @@ func CheckResourceAttrFunc(name string, key string, test func(string) error) res if !ok { return fmt.Errorf("resource not found: %s", name) } + value, ok := rs.Primary.Attributes[key] if !ok { return fmt.Errorf("key not found: %s", key) } + err := test(value) if err != nil { return fmt.Errorf("test for %s %s did not pass test: %s", name, key, err) diff --git a/internal/acctest/domain.go b/internal/acctest/domain.go index c8962cceef..aa747d2958 100644 --- a/internal/acctest/domain.go +++ b/internal/acctest/domain.go @@ -33,6 +33,7 @@ func init() { // check if the test domain is not a Scaleway reserved domain isReserved := false + for _, reservedDomain := range reservedDomains { if reservedDomain.MatchString(TestDomain) { isReserved = true diff --git a/internal/acctest/fixtures.go b/internal/acctest/fixtures.go index e4c9ea9343..874d941733 100644 --- a/internal/acctest/fixtures.go +++ b/internal/acctest/fixtures.go @@ -64,6 +64,7 @@ func CreateFakeIAMManager(tt *TestTools) (*account.Project, *iam.APIKey, FakeSid iamPolicyName := sdkacctest.RandomWithPrefix("test-acc-scaleway-iam-policy") projectAPI := account.NewProjectAPI(tt.Meta.ScwClient()) + project, err := projectAPI.CreateProject(&account.ProjectAPICreateProjectRequest{ Name: projectName, }) @@ -74,6 +75,7 @@ func CreateFakeIAMManager(tt *TestTools) (*account.Project, *iam.APIKey, FakeSid return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return projectAPI.DeleteProject(&account.ProjectAPIDeleteProjectRequest{ ProjectID: project.ID, @@ -81,6 +83,7 @@ func CreateFakeIAMManager(tt *TestTools) (*account.Project, *iam.APIKey, FakeSid }) iamAPI := iam.NewAPI(tt.Meta.ScwClient()) + iamApplication, err := iamAPI.CreateApplication(&iam.CreateApplicationRequest{ Name: iamApplicationName, }) @@ -91,6 +94,7 @@ func CreateFakeIAMManager(tt *TestTools) (*account.Project, *iam.APIKey, FakeSid return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return iamAPI.DeleteApplication(&iam.DeleteApplicationRequest{ ApplicationID: iamApplication.ID, @@ -114,6 +118,7 @@ func CreateFakeIAMManager(tt *TestTools) (*account.Project, *iam.APIKey, FakeSid return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return iamAPI.DeletePolicy(&iam.DeletePolicyRequest{ PolicyID: iamPolicy.ID, @@ -131,6 +136,7 @@ func CreateFakeIAMManager(tt *TestTools) (*account.Project, *iam.APIKey, FakeSid return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return iamAPI.DeleteAPIKey(&iam.DeleteAPIKeyRequest{ AccessKey: iamAPIKey.AccessKey, @@ -163,6 +169,7 @@ func CreateFakeSideProject(tt *TestTools) (*account.Project, *iam.APIKey, FakeSi iamPolicyName := sdkacctest.RandomWithPrefix("test-acc-scaleway-iam-policy") projectAPI := account.NewProjectAPI(tt.Meta.ScwClient()) + project, err := projectAPI.CreateProject(&account.ProjectAPICreateProjectRequest{ Name: projectName, }) @@ -173,6 +180,7 @@ func CreateFakeSideProject(tt *TestTools) (*account.Project, *iam.APIKey, FakeSi return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return projectAPI.DeleteProject(&account.ProjectAPIDeleteProjectRequest{ ProjectID: project.ID, @@ -180,6 +188,7 @@ func CreateFakeSideProject(tt *TestTools) (*account.Project, *iam.APIKey, FakeSi }) iamAPI := iam.NewAPI(tt.Meta.ScwClient()) + iamApplication, err := iamAPI.CreateApplication(&iam.CreateApplicationRequest{ Name: iamApplicationName, }) @@ -190,6 +199,7 @@ func CreateFakeSideProject(tt *TestTools) (*account.Project, *iam.APIKey, FakeSi return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return iamAPI.DeleteApplication(&iam.DeleteApplicationRequest{ ApplicationID: iamApplication.ID, @@ -213,6 +223,7 @@ func CreateFakeSideProject(tt *TestTools) (*account.Project, *iam.APIKey, FakeSi return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return iamAPI.DeletePolicy(&iam.DeletePolicyRequest{ PolicyID: iamPolicy.ID, @@ -230,6 +241,7 @@ func CreateFakeSideProject(tt *TestTools) (*account.Project, *iam.APIKey, FakeSi return nil, nil, nil, err } + terminateFunctions = append(terminateFunctions, func() error { return iamAPI.DeleteAPIKey(&iam.DeleteAPIKeyRequest{ AccessKey: iamAPIKey.AccessKey, diff --git a/internal/acctest/sweepers.go b/internal/acctest/sweepers.go index d84ca0e5b6..0b74a6b4b8 100644 --- a/internal/acctest/sweepers.go +++ b/internal/acctest/sweepers.go @@ -10,6 +10,7 @@ import ( func Sweep(f func(scwClient *scw.Client) error) error { ctx := context.Background() + m, err := meta.NewMeta(ctx, &meta.Config{ TerraformVersion: "terraform-tests", }) @@ -26,6 +27,7 @@ func SweepZones(zones []scw.Zone, f func(scwClient *scw.Client, zone scw.Zone) e if err != nil { return err } + err = f(client, zone) if err != nil { logging.L.Warningf("error running sweepZones, ignoring: %s", err) @@ -52,6 +54,7 @@ func SweepRegions(regions []scw.Region, f func(scwClient *scw.Client, region scw // functions for a given zone func sharedClientForZone(zone scw.Zone) (*scw.Client, error) { ctx := context.Background() + m, err := meta.NewMeta(ctx, &meta.Config{ TerraformVersion: "terraform-tests", ForceZone: zone, diff --git a/internal/acctest/validate_cassettes_test.go b/internal/acctest/validate_cassettes_test.go index 60291b0ba4..3dedec9208 100644 --- a/internal/acctest/validate_cassettes_test.go +++ b/internal/acctest/validate_cassettes_test.go @@ -34,9 +34,11 @@ func exceptionsCassettesCases() map[string]struct{} { func getTestFiles() (map[string]struct{}, error) { filesMap := make(map[string]struct{}) exceptions := exceptionsCassettesCases() + err := filepath.WalkDir("../services", func(path string, _ fs.DirEntry, _ error) error { isCassette := strings.Contains(path, "cassette") _, isException := exceptions[path] + if isCassette && !isException { filesMap[fileNameWithoutExtSuffix(path)] = struct{}{} } @@ -74,6 +76,7 @@ func checkErrorCode(c *cassette.Cassette) error { func checkErrCodeExcept(i *cassette.Interaction, c *cassette.Cassette, codes ...int) bool { exceptions := exceptionsCassettesCases() + _, isException := exceptions[c.File] if isException { return isException @@ -82,6 +85,7 @@ func checkErrCodeExcept(i *cassette.Interaction, c *cassette.Cassette, codes ... if strings.Contains(i.Response.Body, mnq.AWSErrNonExistentQueue) && i.Response.Code == 400 { return true } + if i.Response.Code >= 400 { for _, httpCode := range codes { if i.Response.Code == httpCode { diff --git a/internal/acctest/vcr.go b/internal/acctest/vcr.go index 03c096c92f..6cdadaa7a0 100644 --- a/internal/acctest/vcr.go +++ b/internal/acctest/vcr.go @@ -48,6 +48,7 @@ var BodyMatcherIgnore = []string{ // getTestFilePath returns a valid filename path based on the go test name and suffix. (Take care of non fs friendly char) func getTestFilePath(t *testing.T, pkgFolder string, suffix string) string { t.Helper() + specialChars := regexp.MustCompile(`[\\?%*:|"<>. ]`) // Replace nested tests separators. @@ -91,6 +92,7 @@ func compareFormBodies(expected, actual url.Values) bool { // We do not want to generate new cassettes for each new features continue } + if !compareJSONFields(expectedValue, actual[key]) { return false } @@ -125,6 +127,7 @@ func cassetteBodyMatcher(actualRequest *http.Request, cassetteRequest cassette.R if err != nil { panic(fmt.Errorf("cassette body matcher: failed to copy actualRequest body: %w", err)) // lintignore: R009 } + actualRawBody, err := io.ReadAll(actualBody) if err != nil { panic(fmt.Errorf("cassette body matcher: failed to read actualRequest body: %w", err)) // lintignore: R009 @@ -186,10 +189,12 @@ func cassetteMatcher(actual *http.Request, expected cassette.Request) bool { actualURL := actual.URL actualURLValues := actualURL.Query() expectedURLValues := expectedURL.Query() + for _, query := range QueryMatcherIgnore { actualURLValues.Del(query) expectedURLValues.Del(query) } + actualURL.RawQuery = actualURLValues.Encode() expectedURL.RawQuery = expectedURLValues.Encode() @@ -199,6 +204,7 @@ func cassetteMatcher(actual *http.Request, expected cassette.Request) bool { if !strings.HasSuffix(expectedURL.Host, "scw.cloud") { return false } + actualS3Host := strings.Split(actualURL.Host, ".") expectedS3Host := strings.Split(expectedURL.Host, ".") @@ -212,6 +218,7 @@ func cassetteMatcher(actual *http.Request, expected cassette.Request) bool { if strings.Contains(actualBucket, "-") { actualBucket = actualBucket[:strings.LastIndex(actualBucket, "-")] } + if strings.Contains(expectedBucket, "-") { expectedBucket = expectedBucket[:strings.LastIndex(expectedBucket, "-")] } @@ -230,20 +237,24 @@ func cassetteMatcher(actual *http.Request, expected cassette.Request) bool { func cassetteSensitiveFieldsAnonymizer(i *cassette.Interaction) error { var jsonBody map[string]interface{} + err := json.Unmarshal([]byte(i.Response.Body), &jsonBody) if err != nil { //nolint:nilerr return nil } + for key, value := range SensitiveFields { if _, ok := jsonBody[key]; ok { jsonBody[key] = value } } + anonymizedBody, err := json.Marshal(jsonBody) if err != nil { return fmt.Errorf("failed to marshal anonymized body: %w", err) } + i.Response.Body = string(anonymizedBody) return nil @@ -257,6 +268,7 @@ func cassetteSensitiveFieldsAnonymizer(i *cassette.Interaction) error { // closed and saved after the requests. func getHTTPRecoder(t *testing.T, pkgFolder string, update bool) (client *http.Client, cleanup func(), err error) { t.Helper() + recorderMode := recorder.ModeReplayOnly if update { recorderMode = recorder.ModeRecordOnly diff --git a/internal/datasource/schemas.go b/internal/datasource/schemas.go index 86a843ac07..33b8698ee1 100644 --- a/internal/datasource/schemas.go +++ b/internal/datasource/schemas.go @@ -38,6 +38,7 @@ func NewRegionalID(idI interface{}, fallBackRegion scw.Region) string { // source: https://github.com/hashicorp/terraform-provider-google/blob/main/google/tpgresource/datasource_helpers.go func SchemaFromResourceSchema(rs map[string]*schema.Schema) map[string]*schema.Schema { ds := make(map[string]*schema.Schema, len(rs)) + for k, v := range rs { dv := &schema.Schema{ Computed: true, @@ -69,6 +70,7 @@ func SchemaFromResourceSchema(rs map[string]*schema.Schema) map[string]*schema.S // Elem of all other types are copied as-is dv.Elem = v.Elem } + ds[k] = dv } diff --git a/internal/datasource/search.go b/internal/datasource/search.go index 986628a52f..9087d79021 100644 --- a/internal/datasource/search.go +++ b/internal/datasource/search.go @@ -11,6 +11,7 @@ import ( // It returns the first matching element and an error if either no match is found or multiple matches are found. func FindExact[T any](slice []T, finder func(T) bool, searchName string) (T, error) { //nolint:ireturn var found T + var foundFlag bool for _, elem := range slice { @@ -21,6 +22,7 @@ func FindExact[T any](slice []T, finder func(T) bool, searchName string) (T, err return zero, fmt.Errorf("multiple elements found with the name %s", searchName) } + found = elem foundFlag = true } diff --git a/internal/dsf/list.go b/internal/dsf/list.go index efe811439b..cb71f508c9 100644 --- a/internal/dsf/list.go +++ b/internal/dsf/list.go @@ -35,6 +35,7 @@ func GetStringListsFromState(key string, d *schema.ResourceData) ([]string, []st for i, v := range oldList.([]interface{}) { oldListStr[i] = fmt.Sprint(v) } + for i, v := range newList.([]interface{}) { newListStr[i] = fmt.Sprint(v) } diff --git a/internal/dsf/time.go b/internal/dsf/time.go index 0885eb96eb..8aa9479622 100644 --- a/internal/dsf/time.go +++ b/internal/dsf/time.go @@ -10,8 +10,10 @@ func Duration(_, oldValue, newValue string, _ *schema.ResourceData) bool { if oldValue == newValue { return true } + d1, err1 := time.ParseDuration(oldValue) d2, err2 := time.ParseDuration(newValue) + if err1 != nil || err2 != nil { return false } @@ -23,8 +25,10 @@ func TimeRFC3339(_, oldValue, newValue string, _ *schema.ResourceData) bool { if oldValue == newValue { return true } + t1, err1 := time.Parse(time.RFC3339, oldValue) t2, err2 := time.Parse(time.RFC3339, newValue) + if err1 != nil || err2 != nil { return false } diff --git a/internal/locality/ids.go b/internal/locality/ids.go index 975e61f157..7a7ecca035 100644 --- a/internal/locality/ids.go +++ b/internal/locality/ids.go @@ -12,10 +12,12 @@ func ExpandID(id interface{}) string { func ExpandIDs(data interface{}) []string { expandedIDs := make([]string, 0, len(data.([]interface{}))) + for _, s := range data.([]interface{}) { if s == nil { s = "" } + expandedID := ExpandID(s.(string)) expandedIDs = append(expandedIDs, expandedID) } diff --git a/internal/locality/parsing.go b/internal/locality/parsing.go index 68ed4b4d0d..dd2c590754 100644 --- a/internal/locality/parsing.go +++ b/internal/locality/parsing.go @@ -29,6 +29,7 @@ func ParseLocalizedNestedID(localizedID string) (locality string, innerID, outer func ParseLocalizedNestedOwnerID(localizedID string) (locality string, innerID, outerID string, err error) { tab := strings.Split(localizedID, "/") n := len(tab) + switch n { case 2: locality = tab[0] @@ -52,6 +53,7 @@ func CompareLocalities(loc1, loc2 string) bool { if loc1 == loc2 { return true } + if strings.HasPrefix(loc1, loc2) || strings.HasPrefix(loc2, loc1) { return true } diff --git a/internal/locality/regional/ids.go b/internal/locality/regional/ids.go index c27ab8bde7..5690d5ed0e 100644 --- a/internal/locality/regional/ids.go +++ b/internal/locality/regional/ids.go @@ -28,6 +28,7 @@ func NewID(region scw.Region, id string) ID { func ExpandID(id interface{}) ID { regionalID := ID{} tab := strings.Split(id.(string), "/") + if len(tab) != 2 { regionalID.ID = id.(string) } else { @@ -72,6 +73,7 @@ func NewRegionalIDs(region scw.Region, ids []string) []string { if ids == nil { return nil } + flattenedIDs := make([]string, len(ids)) for i, id := range ids { flattenedIDs[i] = NewIDString(region, id) diff --git a/internal/locality/validation.go b/internal/locality/validation.go index bb5a1e2d88..a3087ab485 100644 --- a/internal/locality/validation.go +++ b/internal/locality/validation.go @@ -11,7 +11,9 @@ import ( func ValidateStringInSliceWithWarning(correctValues []string, field string) schema.SchemaValidateDiagFunc { return func(i interface{}, path cty.Path) diag.Diagnostics { _, rawErr := validation.StringInSlice(correctValues, true)(i, field) + var res diag.Diagnostics + for _, e := range rawErr { res = append(res, diag.Diagnostic{ Severity: diag.Warning, diff --git a/internal/locality/zonal/ids.go b/internal/locality/zonal/ids.go index 853dde7beb..15462642a4 100644 --- a/internal/locality/zonal/ids.go +++ b/internal/locality/zonal/ids.go @@ -28,6 +28,7 @@ func NewID(zone scw.Zone, id string) ID { func ExpandID(id interface{}) ID { zonedID := ID{} tab := strings.Split(id.(string), "/") + if len(tab) != 2 { zonedID.ID = id.(string) } else { diff --git a/internal/meta/extractors.go b/internal/meta/extractors.go index 3df6f62ec7..67e5bae700 100644 --- a/internal/meta/extractors.go +++ b/internal/meta/extractors.go @@ -136,6 +136,7 @@ func getKeyInRawConfigMap(rawConfig map[string]cty.Value, key string, ty cty.Typ if value.IsNull() { return false, false } + if value.True() { return true, true } @@ -145,6 +146,7 @@ func getKeyInRawConfigMap(rawConfig map[string]cty.Value, key string, ty cty.Typ if value.IsNull() { return nil, false } + valueInt, _ := value.AsBigFloat().Int64() return valueInt, true diff --git a/internal/meta/meta.go b/internal/meta/meta.go index 37aefaadcc..ba303b6ebf 100644 --- a/internal/meta/meta.go +++ b/internal/meta/meta.go @@ -92,23 +92,29 @@ func NewMeta(ctx context.Context, config *Config) (*Meta, error) { if err != nil { return nil, err } + if config.ForceZone != "" { region, err := config.ForceZone.Region() if err != nil { return nil, err } + profile.DefaultRegion = scw.StringPtr(region.String()) profile.DefaultZone = scw.StringPtr(config.ForceZone.String()) } + if config.ForceProjectID != "" { profile.DefaultProjectID = scw.StringPtr(config.ForceProjectID) } + if config.ForceOrganizationID != "" { profile.DefaultOrganizationID = scw.StringPtr(config.ForceOrganizationID) } + if config.ForceAccessKey != "" { profile.AccessKey = scw.StringPtr(config.ForceAccessKey) } + if config.ForceSecretKey != "" { profile.SecretKey = scw.StringPtr(config.ForceSecretKey) } @@ -127,6 +133,7 @@ func NewMeta(ctx context.Context, config *Config) (*Meta, error) { if config.HTTPClient != nil { httpClient = config.HTTPClient } + opts = append(opts, scw.WithHTTPClient(httpClient)) scwClient, err := scw.NewClient(opts...) @@ -171,9 +178,11 @@ func loadProfile(ctx context.Context, d *schema.ResourceData) (*scw.Profile, *Cr if err != nil { return nil, nil, err } + envProfile := scw.LoadEnvProfile() providerProfile := &scw.Profile{} + if d != nil { if profileName, exist := d.GetOk("profile"); exist { profileFromConfig, err := config.GetProfile(profileName.(string)) @@ -181,24 +190,31 @@ func loadProfile(ctx context.Context, d *schema.ResourceData) (*scw.Profile, *Cr providerProfile = profileFromConfig } } + if accessKey, exist := d.GetOk("access_key"); exist { providerProfile.AccessKey = scw.StringPtr(accessKey.(string)) } + if secretKey, exist := d.GetOk("secret_key"); exist { providerProfile.SecretKey = scw.StringPtr(secretKey.(string)) } + if projectID, exist := d.GetOk("project_id"); exist { providerProfile.DefaultProjectID = scw.StringPtr(projectID.(string)) } + if orgID, exist := d.GetOk("organization_id"); exist { providerProfile.DefaultOrganizationID = scw.StringPtr(orgID.(string)) } + if region, exist := d.GetOk("region"); exist { providerProfile.DefaultRegion = scw.StringPtr(region.(string)) } + if zone, exist := d.GetOk("zone"); exist { providerProfile.DefaultZone = scw.StringPtr(zone.(string)) } + if apiURL, exist := d.GetOk("api_url"); exist { providerProfile.APIURL = scw.StringPtr(apiURL.(string)) } @@ -213,6 +229,7 @@ func loadProfile(ctx context.Context, d *schema.ResourceData) (*scw.Profile, *Cr (profile.DefaultRegion == nil || *profile.DefaultRegion == "") { zone := scw.Zone(*profile.DefaultZone) tflog.Debug(ctx, fmt.Sprintf("guess region from %s zone", zone)) + region, err := zone.Region() if err == nil { profile.DefaultRegion = scw.StringPtr(region.String()) @@ -259,15 +276,19 @@ func GetCredentialsSource(defaultZoneProfile, activeProfile, providerProfile, en if profile.AccessKey != nil { credentialsSource.AccessKey = source } + if profile.SecretKey != nil { credentialsSource.SecretKey = source } + if profile.DefaultProjectID != nil { credentialsSource.ProjectID = source } + if profile.DefaultRegion != nil { credentialsSource.DefaultRegion = source } + if profile.DefaultZone != nil { credentialsSource.DefaultZone = source } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d2b683deaa..69c3704cde 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -65,11 +65,14 @@ func addBetaResources(provider *schema.Provider) { if !terraformBetaEnabled { return } + betaResources := map[string]*schema.Resource{} betaDataSources := map[string]*schema.Resource{} + for resourceName, resource := range betaResources { provider.ResourcesMap[resourceName] = resource } + for resourceName, resource := range betaDataSources { provider.DataSourcesMap[resourceName] = resource } diff --git a/internal/services/account/project.go b/internal/services/account/project.go index 0106e23d6a..9e5d91ce3d 100644 --- a/internal/services/account/project.go +++ b/internal/services/account/project.go @@ -81,6 +81,7 @@ func resourceAccountProjectCreate(ctx context.Context, d *schema.ResourceData, m func resourceAccountProjectRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { accountAPI := NewProjectAPI(m) + res, err := accountAPI.GetProject(&accountSDK.ProjectAPIGetProjectRequest{ ProjectID: d.Id(), }, scw.WithContext(ctx)) @@ -116,6 +117,7 @@ func resourceAccountProjectUpdate(ctx context.Context, d *schema.ResourceData, m req.Name = types.ExpandUpdatedStringPtr(d.Get("name")) hasChanged = true } + if d.HasChange("description") { req.Description = types.ExpandUpdatedStringPtr(d.Get("description")) hasChanged = true diff --git a/internal/services/account/project_data_source.go b/internal/services/account/project_data_source.go index 6233eab446..7e1931db28 100644 --- a/internal/services/account/project_data_source.go +++ b/internal/services/account/project_data_source.go @@ -43,6 +43,7 @@ func DataSourceAccountProjectRead(ctx context.Context, d *schema.ResourceData, m // required not in schema as we could use default return diag.Errorf("organization_id is required with name") } + res, err := accountAPI.ListProjects(&accountSDK.ProjectAPIListProjectsRequest{ OrganizationID: *orgID, Name: types.ExpandStringPtr(name), diff --git a/internal/services/account/project_data_source_test.go b/internal/services/account/project_data_source_test.go index 831c43896a..d71ba5ba45 100644 --- a/internal/services/account/project_data_source_test.go +++ b/internal/services/account/project_data_source_test.go @@ -11,10 +11,12 @@ import ( func TestAccDataSourceProject_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + orgID, orgIDExists := tt.Meta.ScwClient().GetDefaultOrganizationID() if !orgIDExists { orgID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: tt.ProviderFactories, @@ -50,10 +52,12 @@ func TestAccDataSourceProject_Basic(t *testing.T) { func TestAccDataSourceProject_Default(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + orgID, orgIDExists := tt.Meta.ScwClient().GetDefaultOrganizationID() if !orgIDExists { orgID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: tt.ProviderFactories, diff --git a/internal/services/account/testfuncs/sweep.go b/internal/services/account/testfuncs/sweep.go index 21f16803fb..848247d83b 100644 --- a/internal/services/account/testfuncs/sweep.go +++ b/internal/services/account/testfuncs/sweep.go @@ -24,15 +24,18 @@ func testSweepAccountProject(_ string) error { logging.L.Debugf("sweeper: destroying the project") req := &accountSDK.ProjectAPIListProjectsRequest{} + listProjects, err := accountAPI.ListProjects(req, scw.WithAllPages()) if err != nil { return fmt.Errorf("failed to list projects: %w", err) } + for _, project := range listProjects.Projects { // Do not delete default project if project.ID == req.OrganizationID || !acctest.IsTestResource(project.Name) { continue } + err = accountAPI.DeleteProject(&accountSDK.ProjectAPIDeleteProjectRequest{ ProjectID: project.ID, }) diff --git a/internal/services/applesilicon/server_test.go b/internal/services/applesilicon/server_test.go index 70fe571299..6d79c9ed24 100644 --- a/internal/services/applesilicon/server_test.go +++ b/internal/services/applesilicon/server_test.go @@ -14,6 +14,7 @@ import ( func TestAccServer_Basic(t *testing.T) { t.Skip("Skipping AppleSilicon test as this kind of server can't be deleted before 24h") + tt := acctest.NewTestTools(t) defer tt.Cleanup() resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/applesilicon/testfuncs/sweep.go b/internal/services/applesilicon/testfuncs/sweep.go index 71c099dc50..5df6d5e11a 100644 --- a/internal/services/applesilicon/testfuncs/sweep.go +++ b/internal/services/applesilicon/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepAppleSiliconServer(_ string) error { return acctest.SweepZones([]scw.Zone{scw.ZoneFrPar1}, func(scwClient *scw.Client, zone scw.Zone) error { asAPI := applesiliconSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the apple silicon instance in (%s)", zone) + listServers, err := asAPI.ListServers(&applesiliconSDK.ListServersRequest{Zone: zone}, scw.WithAllPages()) if err != nil { return fmt.Errorf("error listing apple silicon servers in (%s) in sweeper: %s", zone, err) diff --git a/internal/services/baremetal/helpers.go b/internal/services/baremetal/helpers.go index 29f3b1fc84..3bd5db38c1 100644 --- a/internal/services/baremetal/helpers.go +++ b/internal/services/baremetal/helpers.go @@ -79,6 +79,7 @@ func detachAllPrivateNetworkFromServer(ctx context.Context, d *schema.ResourceDa if err != nil { return err } + listPrivateNetwork, err := privateNetworkAPI.ListServerPrivateNetworks(&baremetalV3.PrivateNetworkAPIListServerPrivateNetworksRequest{ Zone: zone, ServerID: &serverID, @@ -199,17 +200,22 @@ func privateNetworkSetHash(v interface{}) int { id := locality.ExpandID(m["id"].(string)) var buf bytes.Buffer + buf.WriteString(id) if ipamIPs, ok := m["ipam_ip_ids"]; ok && ipamIPs != nil { ipamIPsList := ipamIPs.([]interface{}) + var ipamIPIDs []string + for _, ip := range ipamIPsList { if ipStr, ok := ip.(string); ok && ipStr != "" { ipamIPIDs = append(ipamIPIDs, ipStr) } } + sort.Strings(ipamIPIDs) + for _, ipID := range ipamIPIDs { buf.WriteString("-") buf.WriteString(ipID) diff --git a/internal/services/baremetal/offer_data_source.go b/internal/services/baremetal/offer_data_source.go index 523234c583..b5493986d8 100644 --- a/internal/services/baremetal/offer_data_source.go +++ b/internal/services/baremetal/offer_data_source.go @@ -171,6 +171,7 @@ func dataSourceOfferRead(ctx context.Context, d *schema.ResourceData, m interfac } var matches []*baremetal.Offer + for _, offer := range res.Offers { if offer.Name == d.Get("name") { if !offer.Enable && !d.Get("include_disabled").(bool) { diff --git a/internal/services/baremetal/offer_data_source_test.go b/internal/services/baremetal/offer_data_source_test.go index 9a4a4fd76d..9c5ec6ff6c 100644 --- a/internal/services/baremetal/offer_data_source_test.go +++ b/internal/services/baremetal/offer_data_source_test.go @@ -22,9 +22,11 @@ const ( func TestAccDataSourceOffer_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: tt.ProviderFactories, @@ -72,9 +74,11 @@ func TestAccDataSourceOffer_Basic(t *testing.T) { func TestAccDataSourceOffer_SubscriptionPeriodHourly(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: tt.ProviderFactories, @@ -126,9 +130,11 @@ func TestAccDataSourceOffer_SubscriptionPeriodHourly(t *testing.T) { func TestAccDataSourceOffer_SubscriptionPeriodMonthly(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: tt.ProviderFactories, @@ -191,6 +197,7 @@ func isOfferPresent(tt *acctest.TestTools, n string) resource.TestCheckFunc { } api := baremetalSDK.NewAPI(tt.Meta.ScwClient()) + _, err = baremetal.FindOfferByID(context.Background(), api, zone, id) if err != nil { return err diff --git a/internal/services/baremetal/option_data_source.go b/internal/services/baremetal/option_data_source.go index b07f203151..66f4dda27d 100644 --- a/internal/services/baremetal/option_data_source.go +++ b/internal/services/baremetal/option_data_source.go @@ -47,10 +47,13 @@ func dataSourceOptionRead(ctx context.Context, d *schema.ResourceData, m interfa } var optionName string + var optionManageable bool + optionID, ok := d.GetOk("option_id") if ok { optionID = d.Get("option_id") + res, err := api.GetOption(&baremetal.GetOptionRequest{ Zone: zone, OptionID: optionID.(string), @@ -58,6 +61,7 @@ func dataSourceOptionRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + optionManageable = res.Manageable optionName = res.Name } else { @@ -67,9 +71,11 @@ func dataSourceOptionRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + if len(res.Options) == 0 { return diag.FromErr(fmt.Errorf("no option found with the name %s", d.Get("name"))) } + for _, option := range res.Options { if option.Name == d.Get("name") { optionID, optionManageable, optionName = option.ID, option.Manageable, option.Name diff --git a/internal/services/baremetal/option_data_source_test.go b/internal/services/baremetal/option_data_source_test.go index 163e0bda07..0b93359098 100644 --- a/internal/services/baremetal/option_data_source_test.go +++ b/internal/services/baremetal/option_data_source_test.go @@ -62,6 +62,7 @@ func isOptionPresent(tt *acctest.TestTools, n string) resource.TestCheckFunc { } api := baremetal.NewAPI(tt.Meta.ScwClient()) + _, err = api.GetOption(&baremetal.GetOptionRequest{ OptionID: ID, Zone: zone, diff --git a/internal/services/baremetal/os_data_source.go b/internal/services/baremetal/os_data_source.go index e645a8d609..7f5d7b21f2 100644 --- a/internal/services/baremetal/os_data_source.go +++ b/internal/services/baremetal/os_data_source.go @@ -48,10 +48,12 @@ func DataSourceOSRead(ctx context.Context, d *schema.ResourceData, m interface{} } var osVersion, osName string + osID, ok := d.GetOk("os_id") if ok { // We fetch the name and version using the os id osID = d.Get("os_id") + res, err := api.GetOS(&baremetal.GetOSRequest{ Zone: zone, OsID: osID.(string), @@ -59,6 +61,7 @@ func DataSourceOSRead(ctx context.Context, d *schema.ResourceData, m interface{} if err != nil { return diag.FromErr(err) } + osVersion = res.Version osName = res.Name } else { @@ -69,9 +72,11 @@ func DataSourceOSRead(ctx context.Context, d *schema.ResourceData, m interface{} if err != nil { return diag.FromErr(err) } + if len(res.Os) == 0 { return diag.FromErr(fmt.Errorf("no os found with the name %s", d.Get("name"))) } + for _, os := range res.Os { if os.Name == d.Get("name") && os.Version == d.Get("version") { osID, osVersion, osName = os.ID, os.Version, os.Name diff --git a/internal/services/baremetal/os_data_source_test.go b/internal/services/baremetal/os_data_source_test.go index 9b8daaa824..d01948a923 100644 --- a/internal/services/baremetal/os_data_source_test.go +++ b/internal/services/baremetal/os_data_source_test.go @@ -63,6 +63,7 @@ func testAccCheckBaremetalOsExists(tt *acctest.TestTools, n string) resource.Tes } baremetalAPI := baremetal.NewAPI(tt.Meta.ScwClient()) + _, err = baremetalAPI.GetOS(&baremetal.GetOSRequest{ OsID: ID, Zone: zone, diff --git a/internal/services/baremetal/server.go b/internal/services/baremetal/server.go index 3c5d134770..2742874684 100644 --- a/internal/services/baremetal/server.go +++ b/internal/services/baremetal/server.go @@ -318,6 +318,7 @@ func ResourceServerCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + offerID = zonal.NewID(zone, o.ID) } @@ -331,17 +332,21 @@ func ResourceServerCreate(ctx context.Context, d *schema.ResourceData, m interfa } partitioningSchema := baremetal.Schema{} + if file, ok := d.GetOk("partitioning"); ok || !d.Get("install_config_afterward").(bool) { if diags := validateInstallConfig(ctx, d, m); len(diags) > 0 { return diags } + if file != "" { todecode, _ := file.(string) + err = json.Unmarshal([]byte(todecode), &partitioningSchema) if err != nil { return diag.FromErr(err) } } + req.Install = &baremetal.CreateServerRequestInstall{ OsID: zonal.ExpandID(d.Get("os")).ID, Hostname: d.Get("hostname").(string), @@ -364,6 +369,7 @@ func ResourceServerCreate(ctx context.Context, d *schema.ResourceData, m interfa } else { _, err = waitForServerInstall(ctx, api, zone, server.ID, d.Timeout(schema.TimeoutCreate)) } + if err != nil { return diag.FromErr(err) } @@ -374,6 +380,7 @@ func ResourceServerCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + for i := range opSpecs { _, err = api.AddOptionServer(&baremetal.AddOptionServerRequest{ Zone: server.Zone, @@ -468,6 +475,7 @@ func ResourceServerRead(ctx context.Context, d *schema.ResourceData, m interface _ = d.Set("ips", flattenIPs(server.IPs)) _ = d.Set("ipv4", flattenIPv4s(server.IPs)) _ = d.Set("ipv6", flattenIPv6s(server.IPs)) + if server.Install != nil { _ = d.Set("os", zonal.NewIDString(server.Zone, os.ID)) _ = d.Set("os_name", os.Name) @@ -475,6 +483,7 @@ func ResourceServerRead(ctx context.Context, d *schema.ResourceData, m interface _ = d.Set("user", server.Install.User) _ = d.Set("service_user", server.Install.ServiceUser) } + _ = d.Set("description", server.Description) _ = d.Set("options", flattenOptions(server.Zone, server.Options)) @@ -485,10 +494,12 @@ func ResourceServerRead(ctx context.Context, d *schema.ResourceData, m interface if err != nil { return diag.FromErr(fmt.Errorf("failed to list server's private networks: %w", err)) } + pnRegion, err := server.Zone.Region() if err != nil { return diag.FromErr(err) } + _ = d.Set("private_network", flattenPrivateNetworks(pnRegion, listPrivateNetworks.ServerPrivateNetworks)) return nil @@ -522,6 +533,7 @@ func ResourceServerUpdate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + optionsToDelete := compareOptions(options, serverGetOptionIDs) for i := range optionsToDelete { _, err = api.DeleteOptionServer(&baremetal.DeleteOptionServerRequest{ @@ -620,6 +632,7 @@ func ResourceServerUpdate(ctx context.Context, d *schema.ResourceData, m interfa if diags := validateInstallConfig(ctx, d, m); len(diags) > 0 { return diags } + err = installServer(ctx, d, api, installReq) if err != nil { return diag.FromErr(err) @@ -645,6 +658,7 @@ func ResourceServerUpdate(ctx context.Context, d *schema.ResourceData, m interfa if diags := validateInstallConfig(ctx, d, m); len(diags) > 0 { return diags } + err = installServer(ctx, d, api, installReq) if err != nil { return diag.FromErr(err) @@ -717,6 +731,7 @@ func validateInstallConfig(ctx context.Context, d *schema.ResourceData, m interf } diags := diag.Diagnostics(nil) + installAttributes := []struct { Attribute string Field *baremetal.OSOSField diff --git a/internal/services/baremetal/server_data_source.go b/internal/services/baremetal/server_data_source.go index 6e96e31ede..a2f4f91cfd 100644 --- a/internal/services/baremetal/server_data_source.go +++ b/internal/services/baremetal/server_data_source.go @@ -43,6 +43,7 @@ func DataSourceServerRead(ctx context.Context, d *schema.ResourceData, m interfa serverID, ok := d.GetOk("server_id") if !ok { // Get server by zone and name. serverName := d.Get("name").(string) + res, err := api.ListServers(&baremetal.ListServersRequest{ Zone: zone, Name: scw.StringPtr(serverName), @@ -66,14 +67,17 @@ func DataSourceServerRead(ctx context.Context, d *schema.ResourceData, m interfa zoneID := datasource.NewZonedID(serverID, zone) d.SetId(zoneID) + err = d.Set("server_id", zoneID) if err != nil { return diag.FromErr(err) } + diags := ResourceServerRead(ctx, d, m) if diags != nil { return diags } + if d.Id() == "" { return diag.Errorf("baremetal server (%s) not found", zoneID) } diff --git a/internal/services/baremetal/server_data_source_test.go b/internal/services/baremetal/server_data_source_test.go index 0bddb0f9d8..f0842a56df 100644 --- a/internal/services/baremetal/server_data_source_test.go +++ b/internal/services/baremetal/server_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccDataSourceServer_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } diff --git a/internal/services/baremetal/server_test.go b/internal/services/baremetal/server_test.go index a27b3c02e7..56e4fec4e4 100644 --- a/internal/services/baremetal/server_test.go +++ b/internal/services/baremetal/server_test.go @@ -24,6 +24,7 @@ var jsonConfigPartitioning = "{\"disks\":[{\"device\":\"/dev/nvme0n1\",\"partiti func TestAccServer_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -118,6 +119,7 @@ func TestAccServer_Basic(t *testing.T) { func TestAccServer_RequiredInstallConfig(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -179,6 +181,7 @@ func TestAccServer_WithoutInstallConfig(t *testing.T) { func TestAccServer_CreateServerWithCustomInstallConfig(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -236,6 +239,7 @@ func TestAccServer_CreateServerWithCustomInstallConfig(t *testing.T) { func TestAccServer_CreateServerWithOption(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -301,6 +305,7 @@ func TestAccServer_CreateServerWithOption(t *testing.T) { func TestAccServer_AddOption(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -392,6 +397,7 @@ func TestAccServer_AddOption(t *testing.T) { func TestAccServer_AddTwoOptionsThenDeleteOne(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -546,6 +552,7 @@ func TestAccServer_AddTwoOptionsThenDeleteOne(t *testing.T) { func TestAccServer_CreateServerWithPrivateNetwork(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -615,6 +622,7 @@ func TestAccServer_CreateServerWithPrivateNetwork(t *testing.T) { func TestAccServer_AddPrivateNetwork(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -727,6 +735,7 @@ func TestAccServer_AddPrivateNetwork(t *testing.T) { func TestAccServer_AddAnotherPrivateNetwork(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -852,6 +861,7 @@ func TestAccServer_AddAnotherPrivateNetwork(t *testing.T) { func TestAccServer_WithIPAMPrivateNetwork(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + if !IsOfferAvailable(OfferID, Zone, tt) { t.Skip("Offer is out of stock") } @@ -1059,6 +1069,7 @@ func testAccChechPartitioning(tt *acctest.TestTools, n string, source string) re if !ok { return fmt.Errorf("resource not found: %s", n) } + baremetalAPI, zonedID, err := baremetal.NewAPIWithZoneAndID(tt.Meta, rs.Primary.ID) if err != nil { return err @@ -1071,14 +1082,18 @@ func testAccChechPartitioning(tt *acctest.TestTools, n string, source string) re if err != nil { return err } + if server.Install.PartitioningSchema == nil { return fmt.Errorf("server %s has no partitioning schema", n) } + schema := baremetalSDK.Schema{} + err = json.Unmarshal([]byte(source), &schema) if err != nil { return err } + if !reflect.DeepEqual(&schema, server.Install.PartitioningSchema) { return fmt.Errorf("server %s has not custom partitioning install", n) } diff --git a/internal/services/baremetal/testfuncs/sweep.go b/internal/services/baremetal/testfuncs/sweep.go index 3d60459b64..53eadcdf5b 100644 --- a/internal/services/baremetal/testfuncs/sweep.go +++ b/internal/services/baremetal/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepServer(_ string) error { return acctest.SweepZones([]scw.Zone{scw.ZoneFrPar2}, func(scwClient *scw.Client, zone scw.Zone) error { baremetalAPI := baremetalSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the baremetal server in (%s)", zone) + listServers, err := baremetalAPI.ListServers(&baremetalSDK.ListServersRequest{Zone: zone}, scw.WithAllPages()) if err != nil { logging.L.Warningf("error listing servers in (%s) in sweeper: %s", zone, err) diff --git a/internal/services/baremetal/types.go b/internal/services/baremetal/types.go index fd884b2fa9..1165eecccb 100644 --- a/internal/services/baremetal/types.go +++ b/internal/services/baremetal/types.go @@ -17,9 +17,11 @@ func expandOptions(i interface{}) ([]*baremetal.ServerOption, error) { for _, op := range i.(*schema.Set).List() { rawOption := op.(map[string]interface{}) option := &baremetal.ServerOption{} + if optionExpiresAt, hasExpiresAt := rawOption["expires_at"]; hasExpiresAt { option.ExpiresAt = types.ExpandTimePtr(optionExpiresAt) } + id := locality.ExpandID(rawOption["id"].(string)) name := rawOption["name"].(string) @@ -40,6 +42,7 @@ func expandPrivateNetworks(pn interface{}) map[string]*[]string { id := locality.ExpandID(rawPN["id"].(string)) ipamIPIDs := &[]string{} + if ipamIPs, ok := rawPN["ipam_ip_ids"]; ok && ipamIPs != nil { ipamIPsList := ipamIPs.([]interface{}) if len(ipamIPsList) > 0 { @@ -47,9 +50,11 @@ func expandPrivateNetworks(pn interface{}) map[string]*[]string { for i, ip := range ipamIPsList { ips[i] = locality.ExpandID(ip.(string)) } + ipamIPIDs = &ips } } + privateNetworks[id] = ipamIPIDs } @@ -60,6 +65,7 @@ func flattenCPUs(cpus []*baremetal.CPU) interface{} { if cpus == nil { return nil } + flattenedCPUs := []map[string]interface{}(nil) for _, cpu := range cpus { flattenedCPUs = append(flattenedCPUs, map[string]interface{}{ @@ -77,6 +83,7 @@ func flattenDisks(disks []*baremetal.Disk) interface{} { if disks == nil { return nil } + flattenedDisks := []map[string]interface{}(nil) for _, disk := range disks { flattenedDisks = append(flattenedDisks, map[string]interface{}{ @@ -92,6 +99,7 @@ func flattenMemory(memories []*baremetal.Memory) interface{} { if memories == nil { return nil } + flattenedMemories := []map[string]interface{}(nil) for _, memory := range memories { flattenedMemories = append(flattenedMemories, map[string]interface{}{ @@ -109,6 +117,7 @@ func flattenIPs(ips []*baremetal.IP) interface{} { if ips == nil { return nil } + flatIPs := []map[string]interface{}(nil) for _, ip := range ips { flatIPs = append(flatIPs, map[string]interface{}{ @@ -126,7 +135,9 @@ func flattenIPv4s(ips []*baremetal.IP) interface{} { if ips == nil { return nil } + flatIPs := []map[string]interface{}(nil) + for _, ip := range ips { if ip.Version == baremetal.IPVersionIPv4 { flatIPs = append(flatIPs, map[string]interface{}{ @@ -145,7 +156,9 @@ func flattenIPv6s(ips []*baremetal.IP) interface{} { if ips == nil { return nil } + flatIPs := []map[string]interface{}(nil) + for _, ip := range ips { if ip.Version == baremetal.IPVersionIPv6 { flatIPs = append(flatIPs, map[string]interface{}{ @@ -164,6 +177,7 @@ func flattenOptions(zone scw.Zone, options []*baremetal.ServerOption) interface{ if options == nil { return nil } + flattenedOptions := []map[string]interface{}(nil) for _, option := range options { flattenedOptions = append(flattenedOptions, map[string]interface{}{ diff --git a/internal/services/baremetal/waiters.go b/internal/services/baremetal/waiters.go index d26a030330..614380d170 100644 --- a/internal/services/baremetal/waiters.go +++ b/internal/services/baremetal/waiters.go @@ -63,6 +63,7 @@ func waitForServerPrivateNetwork(ctx context.Context, api *baremetalV3.PrivateNe if transport.DefaultWaitRetryInterval != nil { retryInterval = *transport.DefaultWaitRetryInterval } + serverPrivateNetwork, err := api.WaitForServerPrivateNetworks(&baremetalV3.WaitForServerPrivateNetworksRequest{ Zone: zone, ServerID: serverID, diff --git a/internal/services/billing/consumption_data_source.go b/internal/services/billing/consumption_data_source.go index 46e833666c..c0d7dc5560 100644 --- a/internal/services/billing/consumption_data_source.go +++ b/internal/services/billing/consumption_data_source.go @@ -84,6 +84,7 @@ func DataSourceBillingConsumptionsRead(ctx context.Context, d *schema.ResourceDa } consumptions := []interface{}(nil) + for _, consumption := range res.Consumptions { rawConsumption := make(map[string]interface{}) rawConsumption["value"] = consumption.Value.String() diff --git a/internal/services/billing/invoices_data_source.go b/internal/services/billing/invoices_data_source.go index fcc86fd371..c935c99638 100644 --- a/internal/services/billing/invoices_data_source.go +++ b/internal/services/billing/invoices_data_source.go @@ -140,6 +140,7 @@ func DataSourceBillingInvoicesRead(ctx context.Context, d *schema.ResourceData, } invoices := []interface{}(nil) + for _, invoice := range res.Invoices { rawInvoice := make(map[string]interface{}) rawInvoice["id"] = invoice.ID diff --git a/internal/services/block/snapshot.go b/internal/services/block/snapshot.go index fb141de8ac..fcc874ab2a 100644 --- a/internal/services/block/snapshot.go +++ b/internal/services/block/snapshot.go @@ -107,11 +107,13 @@ func ResourceBlockSnapshotRead(ctx context.Context, d *schema.ResourceData, m in _ = d.Set("name", snapshot.Name) _ = d.Set("zone", snapshot.Zone) _ = d.Set("project_id", snapshot.ProjectID) + if snapshot.ParentVolume != nil { _ = d.Set("volume_id", snapshot.ParentVolume.ID) } else { _ = d.Set("volume_id", "") } + _ = d.Set("tags", snapshot.Tags) return nil diff --git a/internal/services/block/snapshot_data_source.go b/internal/services/block/snapshot_data_source.go index 5d9284723a..b78d35c37a 100644 --- a/internal/services/block/snapshot_data_source.go +++ b/internal/services/block/snapshot_data_source.go @@ -48,14 +48,17 @@ func DataSourceBlockSnapshotRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + for _, snapshot := range res.Snapshots { if snapshot.Name == d.Get("name").(string) { if snapshotID != "" { return diag.Errorf("more than 1 snapshot found with the same name %s", d.Get("name")) } + snapshotID = snapshot.ID } } + if snapshotID == "" { return diag.Errorf("no snapshot found with the name %s", d.Get("name")) } @@ -63,6 +66,7 @@ func DataSourceBlockSnapshotRead(ctx context.Context, d *schema.ResourceData, m zoneID := datasource.NewZonedID(snapshotID, zone) d.SetId(zoneID) + err = d.Set("snapshot_id", zoneID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/block/testfuncs/sweep.go b/internal/services/block/testfuncs/sweep.go index a12a27b631..19a7697593 100644 --- a/internal/services/block/testfuncs/sweep.go +++ b/internal/services/block/testfuncs/sweep.go @@ -24,7 +24,9 @@ func AddTestSweepers() { func testSweepBlockVolume(_ string) error { return acctest.SweepZones((&blockSDK.API{}).Zones(), func(scwClient *scw.Client, zone scw.Zone) error { blockAPI := blockSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the block volumes in (%s)", zone) + listVolumes, err := blockAPI.ListVolumes( &blockSDK.ListVolumesRequest{ Zone: zone, @@ -52,7 +54,9 @@ func testSweepBlockVolume(_ string) error { func testSweepSnapshot(_ string) error { return acctest.SweepZones((&blockSDK.API{}).Zones(), func(scwClient *scw.Client, zone scw.Zone) error { blockAPI := blockSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the block snapshots in (%s)", zone) + listSnapshots, err := blockAPI.ListSnapshots( &blockSDK.ListSnapshotsRequest{ Zone: zone, diff --git a/internal/services/block/volume.go b/internal/services/block/volume.go index c00562e12a..6cace4939b 100644 --- a/internal/services/block/volume.go +++ b/internal/services/block/volume.go @@ -145,6 +145,7 @@ func ResourceBlockVolumeRead(ctx context.Context, d *schema.ResourceData, m inte if volume.Specs != nil { _ = d.Set("iops", types.FlattenUint32Ptr(volume.Specs.PerfIops)) } + _ = d.Set("size_in_gb", int(volume.Size/scw.GB)) _ = d.Set("zone", volume.Zone) _ = d.Set("project_id", volume.ProjectID) diff --git a/internal/services/block/volume_data_source.go b/internal/services/block/volume_data_source.go index 40323bbd14..c9d3608a60 100644 --- a/internal/services/block/volume_data_source.go +++ b/internal/services/block/volume_data_source.go @@ -47,14 +47,17 @@ func DataSourceBlockVolumeRead(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } + for _, volume := range res.Volumes { if volume.Name == d.Get("name").(string) { if volumeID != "" { return diag.Errorf("more than 1 volume found with the same name %s", d.Get("name")) } + volumeID = volume.ID } } + if volumeID == "" { return diag.Errorf("no volume found with the name %s", d.Get("name")) } @@ -62,6 +65,7 @@ func DataSourceBlockVolumeRead(ctx context.Context, d *schema.ResourceData, m in zoneID := datasource.NewZonedID(volumeID, zone) d.SetId(zoneID) + err = d.Set("volume_id", zoneID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/cockpit/alert_manager.go b/internal/services/cockpit/alert_manager.go index c40d1c5d02..00c7601a24 100644 --- a/internal/services/cockpit/alert_manager.go +++ b/internal/services/cockpit/alert_manager.go @@ -74,6 +74,7 @@ func ResourceCockpitAlertManagerCreate(ctx context.Context, d *schema.ResourceDa if err != nil { return diag.FromErr(err) } + if EnableManagedAlerts { _, err = api.EnableManagedAlerts(&cockpit.RegionalAPIEnableManagedAlertsRequest{ Region: region, @@ -145,6 +146,7 @@ func ResourceCockpitAlertManagerRead(ctx context.Context, d *schema.ResourceData } var contactPointsList []map[string]interface{} + for _, cp := range contactPoints.ContactPoints { if cp.Email != nil { contactPoint := map[string]interface{}{ @@ -153,6 +155,7 @@ func ResourceCockpitAlertManagerRead(ctx context.Context, d *schema.ResourceData contactPointsList = append(contactPointsList, contactPoint) } } + _ = d.Set("contact_points", contactPointsList) return nil @@ -163,6 +166,7 @@ func ResourceCockpitAlertManagerUpdate(ctx context.Context, d *schema.ResourceDa if err != nil { return diag.FromErr(err) } + projectID := d.Get("project_id").(string) if d.HasChange("enable_managed_alerts") { @@ -178,16 +182,19 @@ func ResourceCockpitAlertManagerUpdate(ctx context.Context, d *schema.ResourceDa ProjectID: projectID, }, scw.WithContext(ctx)) } + if err != nil { return diag.FromErr(err) } } + if d.HasChange("contact_points") { oldContactPointsInterface, newContactPointsInterface := d.GetChange("contact_points") oldContactPoints := oldContactPointsInterface.([]interface{}) newContactPoints := newContactPointsInterface.([]interface{}) oldContactMap := make(map[string]map[string]interface{}) + for _, oldCP := range oldContactPoints { cp := oldCP.(map[string]interface{}) email := cp["email"].(string) @@ -195,11 +202,13 @@ func ResourceCockpitAlertManagerUpdate(ctx context.Context, d *schema.ResourceDa } newContactMap := make(map[string]map[string]interface{}) + for _, newCP := range newContactPoints { cp := newCP.(map[string]interface{}) email := cp["email"].(string) newContactMap[email] = cp } + for email := range oldContactMap { if _, found := newContactMap[email]; !found { err := api.DeleteContactPoint(&cockpit.RegionalAPIDeleteContactPointRequest{ @@ -216,6 +225,7 @@ func ResourceCockpitAlertManagerUpdate(ctx context.Context, d *schema.ResourceDa for email := range newContactMap { if _, found := oldContactMap[email]; !found { contactPointEmail := &cockpit.ContactPointEmail{To: email} + _, err = api.CreateContactPoint(&cockpit.RegionalAPICreateContactPointRequest{ Region: region, ProjectID: projectID, @@ -267,6 +277,7 @@ func ResourceCockpitAlertManagerDelete(ctx context.Context, d *schema.ResourceDa if err != nil { return diag.FromErr(err) } + _, err = api.DisableAlertManager(&cockpit.RegionalAPIDisableAlertManagerRequest{ Region: region, ProjectID: projectID, diff --git a/internal/services/cockpit/alert_manager_test.go b/internal/services/cockpit/alert_manager_test.go index 9bc9378c64..4822b10811 100644 --- a/internal/services/cockpit/alert_manager_test.go +++ b/internal/services/cockpit/alert_manager_test.go @@ -137,6 +137,7 @@ func TestAccCockpitAlertManager_UpdateSingleContact(t *testing.T) { func TestAccCockpitAlertManager_EnableDisable(t *testing.T) { t.Skip("TestAccCockpit_WithSourceEndpoints skipped: encountered repeated HTTP 500 errors from the Scaleway Cockpit API.") + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -216,6 +217,7 @@ func testAccCheckAlertManagerEnabled(tt *acctest.TestTools, resourceName string, if err != nil { return err } + if alertManager.ManagedAlertsEnabled != expectedEnabled { return fmt.Errorf("alert manager enabled state %t does not match expected state %t", alertManager.AlertManagerEnabled, expectedEnabled) } @@ -240,6 +242,7 @@ func testAccCheckCockpitContactPointExists(tt *acctest.TestTools, resourceName s if err != nil { return err } + for _, cp := range contactPoints.ContactPoints { if cp.Email != nil && cp.Email.To == rs.Primary.Attributes["contact_points.0.email"] { return nil @@ -272,6 +275,7 @@ func testAccCockpitAlertManagerAndContactsDestroy(tt *acctest.TestTools) resourc if alertManager == nil { return nil } + if alertManager.AlertManagerEnabled { return fmt.Errorf("cockpit alert manager (%s) is still enabled", rs.Primary.ID) } diff --git a/internal/services/cockpit/cockpit.go b/internal/services/cockpit/cockpit.go index 1f6d475751..11870f6f35 100644 --- a/internal/services/cockpit/cockpit.go +++ b/internal/services/cockpit/cockpit.go @@ -109,6 +109,7 @@ func ResourceCockpitCreate(ctx context.Context, d *schema.ResourceData, m interf } var planName string + for _, plan := range plans.Plans { if plan.Name.String() == targetPlan { planName = plan.Name.String() @@ -151,12 +152,14 @@ func ResourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interfac return diag.FromErr(err) } } + res, err := api.GetCurrentPlan(&cockpit.GlobalAPIGetCurrentPlanRequest{ //nolint:staticcheck ProjectID: projectID, }, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) } + _ = d.Set("plan", res.Name.String()) _ = d.Set("plan_id", res.Name.String()) @@ -168,6 +171,7 @@ func ResourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } + _ = d.Set("project_id", projectID) d.SetId(projectID) @@ -177,6 +181,7 @@ func ResourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } + if grafana.GrafanaURL == "" { grafana.GrafanaURL = createGrafanaURL(projectID, region) } @@ -187,6 +192,7 @@ func ResourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } + alertManagerURL := "" if alertManager.AlertManagerURL != nil { alertManagerURL = *alertManager.AlertManagerURL @@ -220,6 +226,7 @@ func ResourceCockpitUpdate(ctx context.Context, d *schema.ResourceData, m interf } var planName string + for _, plan := range plans.Plans { if plan.Name.String() == targetPlan { planName = plan.Name.String() diff --git a/internal/services/cockpit/cockpit_data_source.go b/internal/services/cockpit/cockpit_data_source.go index 03de1a4a80..264807cd3f 100644 --- a/internal/services/cockpit/cockpit_data_source.go +++ b/internal/services/cockpit/cockpit_data_source.go @@ -40,6 +40,7 @@ func dataSourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interf if err != nil { return diag.FromErr(err) } + regionalAPI, region, err := cockpitAPIWithRegion(d, m) if err != nil { return diag.FromErr(err) @@ -65,6 +66,7 @@ func dataSourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interf return diag.FromErr(err) } + _ = d.Set("project_id", d.Get("project_id").(string)) _ = d.Set("plan", res.Name) _ = d.Set("plan_id", res.Name) @@ -84,6 +86,7 @@ func dataSourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interf if err != nil { return diag.FromErr(err) } + if grafana.GrafanaURL == "" { grafana.GrafanaURL = createGrafanaURL(projectID, region) } @@ -94,6 +97,7 @@ func dataSourceCockpitRead(ctx context.Context, d *schema.ResourceData, m interf if err != nil { return diag.FromErr(err) } + alertManagerURL := "" if alertManager.AlertManagerURL != nil { alertManagerURL = *alertManager.AlertManagerURL diff --git a/internal/services/cockpit/cockpit_test.go b/internal/services/cockpit/cockpit_test.go index 1468024157..72f79f5b85 100644 --- a/internal/services/cockpit/cockpit_test.go +++ b/internal/services/cockpit/cockpit_test.go @@ -69,6 +69,7 @@ func TestAccCockpit_Basic(t *testing.T) { func TestAccCockpit_WithSourceEndpoints(t *testing.T) { t.Skip("TestAccCockpit_WithSourceEndpoints skipped: encountered repeated HTTP 500 errors from the Scaleway Cockpit API.") + tt := acctest.NewTestTools(t) defer tt.Cleanup() diff --git a/internal/services/cockpit/grafana_user.go b/internal/services/cockpit/grafana_user.go index 6a9536d6a0..257cebec79 100644 --- a/internal/services/cockpit/grafana_user.go +++ b/internal/services/cockpit/grafana_user.go @@ -106,6 +106,7 @@ func ResourceCockpitGrafanaUserRead(ctx context.Context, d *schema.ResourceData, } var grafanaUser *cockpit.GrafanaUser + for _, user := range res.GrafanaUsers { if user.ID == grafanaUserID { grafanaUser = user diff --git a/internal/services/cockpit/grafana_user_test.go b/internal/services/cockpit/grafana_user_test.go index b61f0f30f2..16ce139035 100644 --- a/internal/services/cockpit/grafana_user_test.go +++ b/internal/services/cockpit/grafana_user_test.go @@ -142,6 +142,7 @@ func isGrafanaUserPresent(tt *acctest.TestTools, n string) resource.TestCheckFun } var grafanaUser *cockpitSDK.GrafanaUser + for _, user := range res.GrafanaUsers { if user.ID == grafanaUserID { grafanaUser = user diff --git a/internal/services/cockpit/helpers_cockpit.go b/internal/services/cockpit/helpers_cockpit.go index 78a7e6d784..0b4801727d 100644 --- a/internal/services/cockpit/helpers_cockpit.go +++ b/internal/services/cockpit/helpers_cockpit.go @@ -117,6 +117,7 @@ func cockpitTokenV1UpgradeFunc(_ context.Context, rawState map[string]interface{ func getDefaultProjectID(ctx context.Context, m interface{}) (string, error) { accountAPI := account.NewProjectAPI(m) + res, err := accountAPI.ListProjects(&accountSDK.ProjectAPIListProjectsRequest{ Name: types.ExpandStringPtr("default"), }, scw.WithContext(ctx)) diff --git a/internal/services/cockpit/plan_data_source.go b/internal/services/cockpit/plan_data_source.go index b6e0a181d3..ff85c54ae2 100644 --- a/internal/services/cockpit/plan_data_source.go +++ b/internal/services/cockpit/plan_data_source.go @@ -37,6 +37,7 @@ func DataSourceCockpitPlanRead(ctx context.Context, d *schema.ResourceData, m in } var plan *cockpit.Plan + for _, p := range res.Plans { if p.Name.String() == name { plan = p diff --git a/internal/services/cockpit/source.go b/internal/services/cockpit/source.go index 7f4b4c73e6..d81cc5c85f 100644 --- a/internal/services/cockpit/source.go +++ b/internal/services/cockpit/source.go @@ -94,6 +94,7 @@ func ResourceCockpitSourceCreate(ctx context.Context, d *schema.ResourceData, me } retentionDays := uint32(d.Get("retention_days").(int)) + res, err := api.CreateDataSource(&cockpit.RegionalAPICreateDataSourceRequest{ Region: region, ProjectID: d.Get("project_id").(string), diff --git a/internal/services/cockpit/source_data_source.go b/internal/services/cockpit/source_data_source.go index 8d72c464bb..a482abd26f 100644 --- a/internal/services/cockpit/source_data_source.go +++ b/internal/services/cockpit/source_data_source.go @@ -92,11 +92,14 @@ func dataSourceCockpitSourceRead(ctx context.Context, d *schema.ResourceData, me func fetchDataSourceByID(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { regionalID := d.Get("id").(string) + api, region, id, err := NewAPIWithRegionAndID(meta, regionalID) if err != nil { return diag.FromErr(err) } + d.SetId(id) + res, err := api.GetDataSource(&cockpit.RegionalAPIGetDataSourceRequest{ Region: region, DataSourceID: id, @@ -104,6 +107,7 @@ func fetchDataSourceByID(ctx context.Context, d *schema.ResourceData, meta inter if err != nil { return diag.FromErr(err) } + flattenDataSource(d, res) return nil @@ -123,6 +127,7 @@ func fetchDataSourceByFilters(ctx context.Context, d *schema.ResourceData, meta if v, ok := d.GetOk("type"); ok { req.Types = []cockpit.DataSourceType{cockpit.DataSourceType(v.(string))} } + if v, ok := d.GetOk("origin"); ok { req.Origin = cockpit.DataSourceOrigin(v.(string)) } diff --git a/internal/services/cockpit/source_data_source_test.go b/internal/services/cockpit/source_data_source_test.go index 45ba20ed53..c2976e103f 100644 --- a/internal/services/cockpit/source_data_source_test.go +++ b/internal/services/cockpit/source_data_source_test.go @@ -85,10 +85,12 @@ func TestAccCockpitSource_DataSource_ByName(t *testing.T) { func TestAccCockpitSource_DataSource_Defaults(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + orgID, orgIDExists := tt.Meta.ScwClient().GetDefaultOrganizationID() if !orgIDExists { orgID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProviderFactories: tt.ProviderFactories, diff --git a/internal/services/cockpit/token.go b/internal/services/cockpit/token.go index 9c90d9e850..43d8e2271f 100644 --- a/internal/services/cockpit/token.go +++ b/internal/services/cockpit/token.go @@ -151,6 +151,7 @@ func ResourceCockpitTokenCreate(ctx context.Context, d *schema.ResourceData, m i rawScopes, scopesSet := d.GetOk("scopes") var scopes []cockpit.TokenScope + if !scopesSet || len(rawScopes.([]interface{})) == 0 { schema := resourceCockpitTokenScopes().Schema for key, val := range schema { diff --git a/internal/services/container/container.go b/internal/services/container/container.go index 245b8fe0d7..27ce435e8a 100644 --- a/internal/services/container/container.go +++ b/internal/services/container/container.go @@ -228,6 +228,7 @@ func ResourceContainerCreate(ctx context.Context, d *schema.ResourceData, m inte ContainerID: res.ID, Redeploy: types.ExpandBoolPtr(shouldDeploy), } + _, err = api.UpdateContainer(reqUpdate, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/container/container_data_source.go b/internal/services/container/container_data_source.go index 8ed4aab576..422431d95e 100644 --- a/internal/services/container/container_data_source.go +++ b/internal/services/container/container_data_source.go @@ -54,8 +54,10 @@ func DataSourceContainerRead(ctx context.Context, d *schema.ResourceData, m inte containerID, ok := d.GetOk("container_id") namespaceID := d.Get("namespace_id") + if !ok { containerName := d.Get("name").(string) + res, err := api.ListContainers(&container.ListContainersRequest{ Region: region, Name: types.ExpandStringPtr(containerName), diff --git a/internal/services/container/cron.go b/internal/services/container/cron.go index a7ea8f3730..2f818f9d4a 100644 --- a/internal/services/container/cron.go +++ b/internal/services/container/cron.go @@ -93,10 +93,12 @@ func ResourceContainerCronCreate(ctx context.Context, d *schema.ResourceData, m } tflog.Info(ctx, fmt.Sprintf("[INFO] Submitted new cron job: %#v", res.Schedule)) + _, err = waitForCron(ctx, api, res.ID, region, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) } + tflog.Info(ctx, "[INFO] cron job ready") d.SetId(regional.NewIDString(region, res.ID)) @@ -148,6 +150,7 @@ func ResourceContainerCronUpdate(ctx context.Context, d *schema.ResourceData, m } shouldUpdate := false + if d.HasChange("schedule") { req.Schedule = scw.StringPtr(d.Get("schedule").(string)) shouldUpdate = true @@ -158,6 +161,7 @@ func ResourceContainerCronUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + shouldUpdate = true req.Args = &jsonObj } @@ -174,11 +178,13 @@ func ResourceContainerCronUpdate(ctx context.Context, d *schema.ResourceData, m } tflog.Info(ctx, fmt.Sprintf("[INFO] Updated cron job: %#v", req.Schedule)) + _, err = waitForCron(ctx, api, cron.ID, region, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) } } + tflog.Info(ctx, "[INFO] cron job ready") return ResourceContainerCronRead(ctx, d, m) @@ -202,6 +208,7 @@ func ResourceContainerCronDelete(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + tflog.Info(ctx, "[INFO] cron job deleted") return nil diff --git a/internal/services/container/namespace.go b/internal/services/container/namespace.go index b792e2a0cf..8e9633f13a 100644 --- a/internal/services/container/namespace.go +++ b/internal/services/container/namespace.go @@ -250,6 +250,7 @@ func ResourceContainerNamespaceDelete(ctx context.Context, d *schema.ResourceDat if err != nil && !httperrors.Is404(err) { return diag.FromErr(err) } + _, err = registry.WaitForNamespace(ctx, registryAPI, region, registryID, d.Timeout(schema.TimeoutDelete)) if err != nil && !httperrors.Is404(err) { return diag.FromErr(err) diff --git a/internal/services/container/namespace_data_source.go b/internal/services/container/namespace_data_source.go index 26b225f59a..3709a3557e 100644 --- a/internal/services/container/namespace_data_source.go +++ b/internal/services/container/namespace_data_source.go @@ -42,6 +42,7 @@ func DataSourceContainerNamespaceRead(ctx context.Context, d *schema.ResourceDat namespaceID, ok := d.GetOk("namespace_id") if !ok { namespaceName := d.Get("name").(string) + res, err := api.ListNamespaces(&container.ListNamespacesRequest{ Region: region, Name: types.ExpandStringPtr(namespaceName), diff --git a/internal/services/container/testfuncs/checks.go b/internal/services/container/testfuncs/checks.go index 47ff3d1e54..d87964470c 100644 --- a/internal/services/container/testfuncs/checks.go +++ b/internal/services/container/testfuncs/checks.go @@ -33,6 +33,7 @@ func TestConfigContainerNamespace(tt *acctest.TestTools, n string) resource.Test if !ok { return fmt.Errorf("not found: %s", n) } + api, region, id, err := container.NewAPIWithRegionAndID(tt.Meta, rs.Primary.ID) if err != nil { return err @@ -47,6 +48,7 @@ func TestConfigContainerNamespace(tt *acctest.TestTools, n string) resource.Test } meta := tt.Meta + var errorMessage registry.ErrorRegistryMessage accessKey, _ := meta.ScwClient().GetAccessKey() @@ -78,11 +80,13 @@ func TestConfigContainerNamespace(tt *acctest.TestTools, n string) resource.Test defer out.Close() buffIOReader := bufio.NewReader(out) + for { streamBytes, errPull := buffIOReader.ReadBytes('\n') if errPull == io.EOF { break } + err = json.Unmarshal(streamBytes, &errorMessage) if err != nil { return fmt.Errorf("could not unmarshal: %v", err) @@ -109,11 +113,13 @@ func TestConfigContainerNamespace(tt *acctest.TestTools, n string) resource.Test defer pusher.Close() buffIOReader = bufio.NewReader(pusher) + for { streamBytes, errPush := buffIOReader.ReadBytes('\n') if errPush == io.EOF { break } + err = json.Unmarshal(streamBytes, &errorMessage) if err != nil { return fmt.Errorf("could not unmarshal: %v", err) diff --git a/internal/services/container/testfuncs/sweep.go b/internal/services/container/testfuncs/sweep.go index 2a241ed068..588d953002 100644 --- a/internal/services/container/testfuncs/sweep.go +++ b/internal/services/container/testfuncs/sweep.go @@ -29,7 +29,9 @@ func AddTestSweepers() { func testSweepTrigger(_ string) error { return acctest.SweepRegions((&containerSDK.API{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { containerAPI := containerSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the container triggers in (%s)", region) + listTriggers, err := containerAPI.ListTriggers( &containerSDK.ListTriggersRequest{ Region: region, @@ -57,7 +59,9 @@ func testSweepTrigger(_ string) error { func testSweepContainer(_ string) error { return acctest.SweepRegions(scw.AllRegions, func(scwClient *scw.Client, region scw.Region) error { containerAPI := containerSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the container in (%s)", region) + listNamespaces, err := containerAPI.ListContainers( &containerSDK.ListContainersRequest{ Region: region, @@ -85,7 +89,9 @@ func testSweepContainer(_ string) error { func testSweepNamespace(_ string) error { return acctest.SweepRegions([]scw.Region{scw.RegionFrPar}, func(scwClient *scw.Client, region scw.Region) error { containerAPI := containerSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the container namespaces in (%s)", region) + listNamespaces, err := containerAPI.ListNamespaces( &containerSDK.ListNamespacesRequest{ Region: region, diff --git a/internal/services/container/token.go b/internal/services/container/token.go index 7012b72de7..3b4c7bc784 100644 --- a/internal/services/container/token.go +++ b/internal/services/container/token.go @@ -93,6 +93,7 @@ func ResourceContainerTokenRead(ctx context.Context, d *schema.ResourceData, m i if err != nil { return diag.FromErr(err) } + token, err := api.GetToken(&container.GetTokenRequest{ Region: region, TokenID: ID, diff --git a/internal/services/container/token_test.go b/internal/services/container/token_test.go index d7f4b5e004..aac82d2f99 100644 --- a/internal/services/container/token_test.go +++ b/internal/services/container/token_test.go @@ -16,6 +16,7 @@ import ( func TestAccToken_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + expiresAt := time.Now().Add(time.Hour * 24).Format(time.RFC3339) if !*acctest.UpdateCassettes { // This hardcoded value has to be replaced with the expiration in cassettes. diff --git a/internal/services/container/trigger.go b/internal/services/container/trigger.go index 15f29bb285..d34bece313 100644 --- a/internal/services/container/trigger.go +++ b/internal/services/container/trigger.go @@ -211,6 +211,7 @@ func ResourceContainerTriggerRead(ctx context.Context, d *schema.ResourceData, m if trigger.ErrorMessage != nil { errMsg = *trigger.ErrorMessage } + diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, Summary: "Trigger in error state", diff --git a/internal/services/domain/helpers.go b/internal/services/domain/helpers.go index 5cadd1abc8..86dabdd091 100644 --- a/internal/services/domain/helpers.go +++ b/internal/services/domain/helpers.go @@ -16,13 +16,16 @@ func NewDomainAPI(m interface{}) *domain.API { func getRecordFromTypeAndData(dnsType domain.RecordType, data string, records []*domain.Record) (*domain.Record, error) { var currentRecord *domain.Record + for _, r := range records { flattedData := flattenDomainData(strings.ToLower(r.Data), r.Type).(string) flattenCurrentData := flattenDomainData(strings.ToLower(data), r.Type).(string) + if strings.HasPrefix(flattedData, flattenCurrentData) && r.Type == dnsType { if currentRecord != nil { return nil, errors.New("multiple records found with same type and data") } + currentRecord = r break diff --git a/internal/services/domain/record.go b/internal/services/domain/record.go index 30a2bc67cf..b385ceb9be 100644 --- a/internal/services/domain/record.go +++ b/internal/services/domain/record.go @@ -268,6 +268,7 @@ func resourceRecordCreate(ctx context.Context, d *schema.ResourceData, m interfa ViewConfig: expandDomainView(d.GetOk("view")), Comment: nil, } + _, err := domainAPI.UpdateDNSZoneRecords(&domain.UpdateDNSZoneRecordsRequest{ DNSZone: dnsZone, Changes: []*domain.RecordChange{ @@ -317,9 +318,13 @@ func resourceRecordCreate(ctx context.Context, d *schema.ResourceData, m interfa func resourceDomainRecordRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { domainAPI := NewDomainAPI(m) + var record *domain.Record + var dnsZone string + var projectID string + var err error currentData := d.Get("data") @@ -332,6 +337,7 @@ func resourceDomainRecordRead(ctx context.Context, d *schema.ResourceData, m int dnsZone = tab[0] recordID := tab[1] + res, err := domainAPI.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ DNSZone: dnsZone, ID: &recordID, @@ -356,12 +362,14 @@ func resourceDomainRecordRead(ctx context.Context, d *schema.ResourceData, m int if !recordTypeExist { return diag.FromErr(errors.New("record type not found")) } + recordType := domain.RecordType(recordTypeRaw.(string)) if recordType == domain.RecordTypeUnknown { return diag.FromErr(errors.New("record type unknow")) } idRecord := locality.ExpandID(d.Id()) + res, err := domainAPI.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ DNSZone: dnsZone, Name: d.Get("name").(string), @@ -421,6 +429,7 @@ func resourceDomainRecordRead(ctx context.Context, d *schema.ResourceData, m int _ = d.Set("weighted", flattenDomainWeighted(record.WeightedConfig)) _ = d.Set("view", flattenDomainView(record.ViewConfig)) _ = d.Set("project_id", projectID) + if record.Name == "" || record.Name == "@" { _ = d.Set("fqdn", dnsZone) } else { @@ -481,6 +490,7 @@ func resourceDomainRecordDelete(ctx context.Context, d *schema.ResourceData, m i domainAPI := NewDomainAPI(m) recordID := locality.ExpandID(d.Id()) + _, err := domainAPI.UpdateDNSZoneRecords(&domain.UpdateDNSZoneRecordsRequest{ DNSZone: d.Get("dns_zone").(string), Changes: []*domain.RecordChange{ @@ -495,6 +505,7 @@ func resourceDomainRecordDelete(ctx context.Context, d *schema.ResourceData, m i if err != nil { return diag.FromErr(err) } + d.SetId("") // for non-root zone, if the zone have only NS records, then delete the zone @@ -518,6 +529,7 @@ func resourceDomainRecordDelete(ctx context.Context, d *schema.ResourceData, m i // The zone isn't empty, keep it return nil } + tflog.Debug(ctx, fmt.Sprintf("record [%s], type [%s]", r.Name, r.Type)) } diff --git a/internal/services/domain/record_data_source.go b/internal/services/domain/record_data_source.go index ea17fd2b92..c0f0461560 100644 --- a/internal/services/domain/record_data_source.go +++ b/internal/services/domain/record_data_source.go @@ -51,21 +51,27 @@ func DataSourceRecordRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + if len(res.Records) == 0 { return diag.FromErr(fmt.Errorf("no record found with the type %s", d.Get("type"))) } + var record *domain.Record + for i := range res.Records { if res.Records[i].Data == d.Get("data").(string) { if record != nil { return diag.FromErr(fmt.Errorf("more than one record found with this name: %s, type: %s and data: %s", d.Get("name"), d.Get("type"), d.Get("data"))) } + record = res.Records[i] } } + if record == nil { return diag.FromErr(fmt.Errorf("no record found with the type this name: %s, type: %s and data: %s", d.Get("name"), d.Get("type"), d.Get("data"))) } + recordID = record.ID } diff --git a/internal/services/domain/record_test.go b/internal/services/domain/record_test.go index ded70bb407..bdfe2084c2 100644 --- a/internal/services/domain/record_test.go +++ b/internal/services/domain/record_test.go @@ -700,6 +700,7 @@ func testAccCheckDomainRecordExists(tt *acctest.TestTools, n string) resource.Te } domainAPI := domain.NewDomainAPI(tt.Meta) + listDNSZones, err := domainAPI.ListDNSZoneRecords(&domainSDK.ListDNSZoneRecordsRequest{ DNSZone: rs.Primary.Attributes["dns_zone"], }) @@ -818,6 +819,7 @@ func testAccCheckDomainRecordDestroy(tt *acctest.TestTools) resource.TestCheckFu // check if the zone still exists domainAPI := domain.NewDomainAPI(tt.Meta) + listDNSZones, err := domainAPI.ListDNSZoneRecords(&domainSDK.ListDNSZoneRecordsRequest{ DNSZone: rs.Primary.Attributes["dns_zone"], }) diff --git a/internal/services/domain/types.go b/internal/services/domain/types.go index 3478411911..9922722966 100644 --- a/internal/services/domain/types.go +++ b/internal/services/domain/types.go @@ -33,6 +33,7 @@ func flattenDomainGeoIP(config *domain.RecordGeoIPConfig) interface{} { if len(config.Matches) > 0 { matches := []map[string]interface{}{} + for _, match := range config.Matches { rawMatch := map[string]interface{}{ "data": match.Data, @@ -40,11 +41,14 @@ func flattenDomainGeoIP(config *domain.RecordGeoIPConfig) interface{} { if len(match.Continents) > 0 { rawMatch["continents"] = match.Continents } + if len(match.Countries) > 0 { rawMatch["countries"] = match.Countries } + matches = append(matches, rawMatch) } + flattenedResult[0]["matches"] = matches } @@ -68,6 +72,7 @@ func expandDomainGeoIPConfig(defaultData string, i interface{}, ok bool) *domain } matches := []*domain.RecordGeoIPConfigMatch{} + for _, rawMatch := range rawMatches { rawMatchMap := rawMatch.(map[string]interface{}) @@ -82,6 +87,7 @@ func expandDomainGeoIPConfig(defaultData string, i interface{}, ok bool) *domain match.Continents = append(match.Continents, rawContinent.(string)) } } + rawCountries, ok := rawMatchMap["countries"].([]interface{}) if ok { match.Countries = []string{} @@ -92,6 +98,7 @@ func expandDomainGeoIPConfig(defaultData string, i interface{}, ok bool) *domain matches = append(matches, match) } + config.Matches = matches return &config @@ -105,6 +112,7 @@ func flattenDomainHTTPService(config *domain.RecordHTTPServiceConfig) interface{ } ips := []interface{}{} + if len(config.IPs) > 0 { for _, ip := range config.IPs { ips = append(ips, ip.String()) @@ -130,6 +138,7 @@ func expandDomainHTTPService(i interface{}, ok bool) *domain.RecordHTTPServiceCo rawMap := i.([]interface{})[0].(map[string]interface{}) ips := []net.IP{} + rawIPs, ok := rawMap["ips"].([]interface{}) if ok { for _, rawIP := range rawIPs { @@ -171,6 +180,7 @@ func expandDomainWeighted(i interface{}, ok bool) *domain.RecordWeightedConfig { } weightedIPs := []*domain.RecordWeightedConfigWeightedIP{} + if raw := i.([]interface{}); len(raw) > 0 { for _, rawWeighted := range raw { rawMap := rawWeighted.(map[string]interface{}) @@ -211,6 +221,7 @@ func expandDomainView(i interface{}, ok bool) *domain.RecordViewConfig { } views := []*domain.RecordViewConfigView{} + if raw := i.([]interface{}); len(raw) > 0 { for _, rawWeighted := range raw { rawMap := rawWeighted.(map[string]interface{}) diff --git a/internal/services/domain/zone.go b/internal/services/domain/zone.go index 12f8ed410e..9ce854463b 100644 --- a/internal/services/domain/zone.go +++ b/internal/services/domain/zone.go @@ -120,6 +120,7 @@ func resourceDomainZoneCreate(ctx context.Context, d *schema.ResourceData, m int return diag.FromErr(err) } + d.SetId(fmt.Sprintf("%s.%s", dnsZone.Subdomain, dnsZone.Domain)) return resourceDomainZoneRead(ctx, d, m) diff --git a/internal/services/domain/zone_test.go b/internal/services/domain/zone_test.go index 31102cb34b..1b617a030d 100644 --- a/internal/services/domain/zone_test.go +++ b/internal/services/domain/zone_test.go @@ -51,6 +51,7 @@ func testAccCheckDomainZoneExists(tt *acctest.TestTools, n string) resource.Test } domainAPI := domain.NewDomainAPI(tt.Meta) + listDNSZones, err := domainAPI.ListDNSZones(&domainSDK.ListDNSZonesRequest{ DNSZones: []string{fmt.Sprintf("%s.%s", rs.Primary.Attributes["subdomain"], rs.Primary.Attributes["domain"])}, }) diff --git a/internal/services/flexibleip/data_source_flexible_ip.go b/internal/services/flexibleip/data_source_flexible_ip.go index 464286c2f6..faf4d0c4e9 100644 --- a/internal/services/flexibleip/data_source_flexible_ip.go +++ b/internal/services/flexibleip/data_source_flexible_ip.go @@ -66,9 +66,11 @@ func DataSourceFlexibleIPRead(ctx context.Context, d *schema.ResourceData, m int if ipID != "" { return diag.Errorf("more than 1 flexible ip found with the same IPv4 address %s", d.Get("ip_address")) } + ipID = ip.ID } } + if ipID == "" { return diag.Errorf("no flexible ip found with the same IPv4 address %s", d.Get("ip_address")) } @@ -76,6 +78,7 @@ func DataSourceFlexibleIPRead(ctx context.Context, d *schema.ResourceData, m int zoneID := datasource.NewZonedID(ipID, zone) d.SetId(zoneID) + err = d.Set("flexible_ip_id", zoneID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/flexibleip/data_source_flexible_ips.go b/internal/services/flexibleip/data_source_flexible_ips.go index 81e0658cf4..fc133c16f7 100644 --- a/internal/services/flexibleip/data_source_flexible_ips.go +++ b/internal/services/flexibleip/data_source_flexible_ips.go @@ -143,26 +143,32 @@ func DataSourceFlexibleIPsRead(ctx context.Context, d *schema.ResourceData, m in } fips := []interface{}(nil) + for _, fip := range res.FlexibleIPs { rawFip := make(map[string]interface{}) rawFip["id"] = zonal.NewID(fip.Zone, fip.ID).String() rawFip["organization_id"] = fip.OrganizationID rawFip["project_id"] = fip.ProjectID rawFip["description"] = fip.Description + if len(fip.Tags) > 0 { rawFip["tags"] = fip.Tags } + rawFip["created_at"] = types.FlattenTime(fip.CreatedAt) rawFip["updated_at"] = types.FlattenTime(fip.UpdatedAt) rawFip["status"] = fip.Status + ip, err := types.FlattenIPNet(fip.IPAddress) if err != nil { return diag.FromErr(err) } + rawFip["ip_address"] = ip if fip.MacAddress != nil { rawFip["mac_address"] = flattenFlexibleIPMacAddress(fip.MacAddress) } + rawFip["reverse"] = fip.Reverse rawFip["zone"] = string(zone) diff --git a/internal/services/flexibleip/ip.go b/internal/services/flexibleip/ip.go index f1c120414b..cfa754743b 100644 --- a/internal/services/flexibleip/ip.go +++ b/internal/services/flexibleip/ip.go @@ -177,6 +177,7 @@ func ResourceFlexibleIPUpdate(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + updateRequest := &flexibleip.UpdateFlexibleIPRequest{ Zone: zone, FipID: flexibleIP.ID, diff --git a/internal/services/flexibleip/ip_test.go b/internal/services/flexibleip/ip_test.go index fd2b883eb7..447da10fb2 100644 --- a/internal/services/flexibleip/ip_test.go +++ b/internal/services/flexibleip/ip_test.go @@ -361,6 +361,7 @@ func testAccCheckFlexibleIPAttachedToBaremetalServer(tt *acctest.TestTools, ipRe if !ok { return fmt.Errorf("resource not found: %s", ipResource) } + serverState, ok := s.RootModule().Resources[serverResource] if !ok { return fmt.Errorf("resource not found: %s", serverResource) @@ -383,6 +384,7 @@ func testAccCheckFlexibleIPAttachedToBaremetalServer(tt *acctest.TestTools, ipRe if err != nil { return err } + ip, err := fipAPI.GetFlexibleIP(&flexibleipSDK.GetFlexibleIPRequest{ FipID: ID, Zone: zone, diff --git a/internal/services/flexibleip/mac_address.go b/internal/services/flexibleip/mac_address.go index a57b0069b7..446b533f09 100644 --- a/internal/services/flexibleip/mac_address.go +++ b/internal/services/flexibleip/mac_address.go @@ -89,6 +89,7 @@ func ResourceFlexibleIPMACCreate(ctx context.Context, d *schema.ResourceData, m } fipID := locality.ExpandID(d.Get("flexible_ip_id")) + _, err = waitFlexibleIP(ctx, fipAPI, zone, fipID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -124,6 +125,7 @@ func ResourceFlexibleIPMACCreate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + _, err = waitFlexibleIP(ctx, fipAPI, zone, locality.ExpandID(dupID), d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -164,6 +166,7 @@ func ResourceFlexibleIPMACRead(ctx context.Context, d *schema.ResourceData, m in _ = d.Set("updated_at", types.FlattenTime(fip.MacAddress.UpdatedAt)) _ = d.Set("zone", fip.MacAddress.Zone) } + _ = d.Set("flexible_ip_ids_to_duplicate", types.ExpandStrings(d.Get("flexible_ip_ids_to_duplicate").(*schema.Set).List())) return nil @@ -212,6 +215,7 @@ func ResourceFlexibleIPMACUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + _, err := fipAPI.DuplicateMACAddr(&flexibleip.DuplicateMACAddrRequest{ Zone: zone, FipID: locality.ExpandID(newID), @@ -220,6 +224,7 @@ func ResourceFlexibleIPMACUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + _, err = waitFlexibleIP(ctx, fipAPI, zone, locality.ExpandID(newID), d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) @@ -236,6 +241,7 @@ func ResourceFlexibleIPMACUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + _, err = waitFlexibleIP(ctx, fipAPI, zone, locality.ExpandID(oldID), d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/flexibleip/mac_address_test.go b/internal/services/flexibleip/mac_address_test.go index 41d7da1e36..f546458714 100644 --- a/internal/services/flexibleip/mac_address_test.go +++ b/internal/services/flexibleip/mac_address_test.go @@ -201,6 +201,7 @@ func testAccCheckFlexibleIPAttachedMACAddress(tt *acctest.TestTools, fipResource if !ok { return fmt.Errorf("resource not found: %s", fipResource) } + macState, ok := s.RootModule().Resources[macResource] if !ok { return fmt.Errorf("resource not found: %s", macResource) @@ -210,6 +211,7 @@ func testAccCheckFlexibleIPAttachedMACAddress(tt *acctest.TestTools, fipResource if err != nil { return err } + ip, err := fipAPI.GetFlexibleIP(&flexibleipSDK.GetFlexibleIPRequest{ FipID: ID, Zone: zone, diff --git a/internal/services/function/cron.go b/internal/services/function/cron.go index 91e389ee5f..e881e12b5f 100644 --- a/internal/services/function/cron.go +++ b/internal/services/function/cron.go @@ -73,6 +73,7 @@ func ResourceFunctionCronCreate(ctx context.Context, d *schema.ResourceData, m i } functionID := locality.ExpandID(d.Get("function_id").(string)) + f, err := waitForFunction(ctx, api, region, functionID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -90,6 +91,7 @@ func ResourceFunctionCronCreate(ctx context.Context, d *schema.ResourceData, m i if err != nil { return diag.FromErr(err) } + request.Args = &jsonObj } @@ -128,6 +130,7 @@ func ResourceFunctionCronRead(ctx context.Context, d *schema.ResourceData, m int _ = d.Set("function_id", regional.NewID(region, cron.FunctionID).String()) _ = d.Set("schedule", cron.Schedule) _ = d.Set("name", cron.Name) + args, err := scw.EncodeJSONObject(*cron.Args, scw.NoEscape) if err != nil { return diag.FromErr(err) @@ -156,10 +159,12 @@ func ResourceFunctionCronUpdate(ctx context.Context, d *schema.ResourceData, m i CronID: cron.ID, } shouldUpdate := false + if d.HasChange("name") { req.Name = types.ExpandStringPtr(d.Get("name").(string)) shouldUpdate = true } + if d.HasChange("schedule") { req.Schedule = types.ExpandStringPtr(d.Get("schedule").(string)) shouldUpdate = true @@ -170,6 +175,7 @@ func ResourceFunctionCronUpdate(ctx context.Context, d *schema.ResourceData, m i if err != nil { return diag.FromErr(err) } + shouldUpdate = true req.Args = &jsonObj } diff --git a/internal/services/function/data_source_function.go b/internal/services/function/data_source_function.go index 127c42d1a7..9185291cbe 100644 --- a/internal/services/function/data_source_function.go +++ b/internal/services/function/data_source_function.go @@ -40,6 +40,7 @@ func DataSourceFunctionRead(ctx context.Context, d *schema.ResourceData, m inter functionID, ok := d.GetOk("function_id") if !ok { functionName := d.Get("name").(string) + res, err := api.ListFunctions(&function.ListFunctionsRequest{ Region: region, NamespaceID: locality.ExpandID(d.Get("namespace_id").(string)), diff --git a/internal/services/function/data_source_function_namespace.go b/internal/services/function/data_source_function_namespace.go index d3ba14846d..940c38ee01 100644 --- a/internal/services/function/data_source_function_namespace.go +++ b/internal/services/function/data_source_function_namespace.go @@ -42,6 +42,7 @@ func DataSourceFunctionNamespaceRead(ctx context.Context, d *schema.ResourceData namespaceID, ok := d.GetOk("namespace_id") if !ok { namespaceName := d.Get("name").(string) + res, err := api.ListNamespaces(&function.ListNamespacesRequest{ Region: region, Name: types.ExpandStringPtr(namespaceName), diff --git a/internal/services/function/domain.go b/internal/services/function/domain.go index 508187cc05..f71b412c7a 100644 --- a/internal/services/function/domain.go +++ b/internal/services/function/domain.go @@ -63,6 +63,7 @@ func ResourceFunctionDomainCreate(ctx context.Context, d *schema.ResourceData, m } functionID := regional.ExpandID(d.Get("function_id").(string)).ID + _, err = waitForFunction(ctx, api, region, functionID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/function/function.go b/internal/services/function/function.go index 538ed43109..6d87c21b73 100644 --- a/internal/services/function/function.go +++ b/internal/services/function/function.go @@ -415,6 +415,7 @@ func ResourceFunctionUpdate(ctx context.Context, d *schema.ResourceData, m inter if err != nil { return nil } + err = functionDeploy(ctx, api, region, f.ID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/function/helpers.go b/internal/services/function/helpers.go index b189bc5c58..bb651c6308 100644 --- a/internal/services/function/helpers.go +++ b/internal/services/function/helpers.go @@ -109,6 +109,7 @@ func functionUpload(ctx context.Context, m interface{}, functionAPI *function.AP "response": string(respDump), "request": string(reqDump), }) + if resp.StatusCode != http.StatusOK { return fmt.Errorf("failed to upload function (Status: %d)", resp.StatusCode) } diff --git a/internal/services/function/testfuncs/sweep.go b/internal/services/function/testfuncs/sweep.go index a9c4c8f41d..7d3fa2808b 100644 --- a/internal/services/function/testfuncs/sweep.go +++ b/internal/services/function/testfuncs/sweep.go @@ -33,7 +33,9 @@ func AddTestSweepers() { func testSweepFunctionTrigger(_ string) error { return acctest.SweepRegions((&functionSDK.API{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { functionAPI := functionSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the function triggers in (%s)", region) + listTriggers, err := functionAPI.ListTriggers( &functionSDK.ListTriggersRequest{ Region: region, @@ -61,7 +63,9 @@ func testSweepFunctionTrigger(_ string) error { func testSweepFunctionNamespace(_ string) error { return acctest.SweepRegions([]scw.Region{scw.RegionFrPar}, func(scwClient *scw.Client, region scw.Region) error { functionAPI := functionSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the function namespaces in (%s)", region) + listNamespaces, err := functionAPI.ListNamespaces( &functionSDK.ListNamespacesRequest{ Region: region, @@ -89,7 +93,9 @@ func testSweepFunctionNamespace(_ string) error { func testSweepFunction(_ string) error { return acctest.SweepRegions([]scw.Region{scw.RegionFrPar}, func(scwClient *scw.Client, region scw.Region) error { functionAPI := functionSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the function in (%s)", region) + listFunctions, err := functionAPI.ListFunctions( &functionSDK.ListFunctionsRequest{ Region: region, @@ -117,7 +123,9 @@ func testSweepFunction(_ string) error { func testSweepFunctionCron(_ string) error { return acctest.SweepRegions([]scw.Region{scw.RegionFrPar}, func(scwClient *scw.Client, region scw.Region) error { functionAPI := functionSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the function cron in (%s)", region) + listCron, err := functionAPI.ListCrons( &functionSDK.ListCronsRequest{ Region: region, diff --git a/internal/services/function/token.go b/internal/services/function/token.go index 86e494f399..1e563947d0 100644 --- a/internal/services/function/token.go +++ b/internal/services/function/token.go @@ -95,6 +95,7 @@ func ResourceFunctionTokenRead(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } + token, err := api.GetToken(&function.GetTokenRequest{ Region: region, TokenID: ID, diff --git a/internal/services/function/token_test.go b/internal/services/function/token_test.go index 1e41a4a8b9..9087ba5d4d 100644 --- a/internal/services/function/token_test.go +++ b/internal/services/function/token_test.go @@ -16,6 +16,7 @@ import ( func TestAccFunctionToken_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + expiresAt := time.Now().Add(time.Hour * 24).Format(time.RFC3339) if !*acctest.UpdateCassettes { // This hardcoded value has to be replaced with the expiration in cassettes. diff --git a/internal/services/function/trigger.go b/internal/services/function/trigger.go index d5603b73ed..e418feafda 100644 --- a/internal/services/function/trigger.go +++ b/internal/services/function/trigger.go @@ -211,6 +211,7 @@ func ResourceFunctionTriggerRead(ctx context.Context, d *schema.ResourceData, m if trigger.ErrorMessage != nil { errMsg = *trigger.ErrorMessage } + diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, Summary: "Trigger in error state", diff --git a/internal/services/function/trigger_test.go b/internal/services/function/trigger_test.go index 751a2622d2..d3292e848f 100644 --- a/internal/services/function/trigger_test.go +++ b/internal/services/function/trigger_test.go @@ -143,6 +143,7 @@ func TestAccFunctionTrigger_Nats(t *testing.T) { func TestAccFunctionTrigger_Error(t *testing.T) { // https://github.com/hashicorp/terraform-plugin-testing/issues/69 t.Skip("Currently cannot test warnings") + tt := acctest.NewTestTools(t) defer tt.Cleanup() diff --git a/internal/services/iam/api_key.go b/internal/services/iam/api_key.go index 1efe7d2ff9..f2791a77f7 100644 --- a/internal/services/iam/api_key.go +++ b/internal/services/iam/api_key.go @@ -91,6 +91,7 @@ func ResourceAPIKey() *schema.Resource { func resourceIamAPIKeyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + res, err := api.CreateAPIKey(&iam.CreateAPIKeyRequest{ ApplicationID: types.ExpandStringPtr(d.Get("application_id")), UserID: types.ExpandStringPtr(d.Get("user_id")), @@ -111,6 +112,7 @@ func resourceIamAPIKeyCreate(ctx context.Context, d *schema.ResourceData, m inte func resourceIamAPIKeyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + res, err := api.GetAPIKey(&iam.GetAPIKeyRequest{ AccessKey: d.Id(), }, scw.WithContext(ctx)) @@ -123,6 +125,7 @@ func resourceIamAPIKeyRead(ctx context.Context, d *schema.ResourceData, m interf return diag.FromErr(err) } + _ = d.Set("description", res.Description) _ = d.Set("created_at", types.FlattenTime(res.CreatedAt)) _ = d.Set("updated_at", types.FlattenTime(res.UpdatedAt)) @@ -132,6 +135,7 @@ func resourceIamAPIKeyRead(ctx context.Context, d *schema.ResourceData, m interf if res.ApplicationID != nil { _ = d.Set("application_id", res.ApplicationID) } + if res.UserID != nil { _ = d.Set("user_id", res.UserID) } diff --git a/internal/services/iam/application.go b/internal/services/iam/application.go index 67d8fc3b5a..068030ffb9 100644 --- a/internal/services/iam/application.go +++ b/internal/services/iam/application.go @@ -64,6 +64,7 @@ func ResourceApplication() *schema.Resource { func resourceIamApplicationCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + app, err := api.CreateApplication(&iam.CreateApplicationRequest{ Name: types.ExpandOrGenerateString(d.Get("name"), "application"), Description: d.Get("description").(string), @@ -81,6 +82,7 @@ func resourceIamApplicationCreate(ctx context.Context, d *schema.ResourceData, m func resourceIamApplicationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + app, err := api.GetApplication(&iam.GetApplicationRequest{ ApplicationID: d.Id(), }, scw.WithContext(ctx)) @@ -93,6 +95,7 @@ func resourceIamApplicationRead(ctx context.Context, d *schema.ResourceData, m i return diag.FromErr(err) } + _ = d.Set("name", app.Name) _ = d.Set("description", app.Description) _ = d.Set("created_at", types.FlattenTime(app.CreatedAt)) @@ -117,10 +120,12 @@ func resourceIamApplicationUpdate(ctx context.Context, d *schema.ResourceData, m req.Name = types.ExpandStringPtr(d.Get("name")) hasChanged = true } + if d.HasChange("description") { req.Description = types.ExpandUpdatedStringPtr(d.Get("description")) hasChanged = true } + if d.HasChange("tags") { req.Tags = types.ExpandUpdatedStringsPtr(d.Get("tags")) hasChanged = true diff --git a/internal/services/iam/application_data_source.go b/internal/services/iam/application_data_source.go index 6575d33c93..9e008dd6bd 100644 --- a/internal/services/iam/application_data_source.go +++ b/internal/services/iam/application_data_source.go @@ -46,6 +46,7 @@ func DataSourceIamApplicationRead(ctx context.Context, d *schema.ResourceData, m if !appIDExists { applicationName := d.Get("name").(string) + res, err := api.ListApplications(&iam.ListApplicationsRequest{ OrganizationID: types.FlattenStringPtr(account.GetOrganizationID(m, d)).(string), Name: types.ExpandStringPtr(applicationName), @@ -67,6 +68,7 @@ func DataSourceIamApplicationRead(ctx context.Context, d *schema.ResourceData, m } d.SetId(appID.(string)) + err := d.Set("application_id", appID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/iam/group.go b/internal/services/iam/group.go index 0d097c71f8..8ff3c3968c 100644 --- a/internal/services/iam/group.go +++ b/internal/services/iam/group.go @@ -90,6 +90,7 @@ func resourceIamGroupCreate(ctx context.Context, d *schema.ResourceData, m inter Description: d.Get("description").(string), Tags: types.ExpandStrings(d.Get("tags")), } + group, err := api.CreateGroup(req, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -99,6 +100,7 @@ func resourceIamGroupCreate(ctx context.Context, d *schema.ResourceData, m inter appIDs := types.ExpandStrings(d.Get("application_ids").(*schema.Set).List()) userIDs := types.ExpandStrings(d.Get("user_ids").(*schema.Set).List()) + if !d.Get("external_membership").(bool) && (len(appIDs) > 0 || len(userIDs) > 0) { _, err := api.SetGroupMembers(&iam.SetGroupMembersRequest{ GroupID: group.ID, @@ -115,6 +117,7 @@ func resourceIamGroupCreate(ctx context.Context, d *schema.ResourceData, m inter func resourceIamGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + group, err := api.GetGroup(&iam.GetGroupRequest{ GroupID: d.Id(), }, scw.WithContext(ctx)) @@ -168,6 +171,7 @@ func resourceIamGroupUpdate(ctx context.Context, d *schema.ResourceData, m inter if !d.Get("external_membership").(bool) && d.HasChanges("application_ids", "user_ids") { appIDs := types.ExpandStrings(d.Get("application_ids").(*schema.Set).List()) userIDs := types.ExpandStrings(d.Get("user_ids").(*schema.Set).List()) + if len(appIDs) > 0 || len(userIDs) > 0 { _, err = api.SetGroupMembers(&iam.SetGroupMembersRequest{ ApplicationIDs: appIDs, @@ -187,6 +191,7 @@ func resourceIamGroupUpdate(ctx context.Context, d *schema.ResourceData, m inter return diag.FromErr(err) } } + for i := range group.UserIDs { _, err = api.RemoveGroupMember(&iam.RemoveGroupMemberRequest{ GroupID: group.ID, diff --git a/internal/services/iam/group_data_source.go b/internal/services/iam/group_data_source.go index 8bfe55103a..5dcd4ce67b 100644 --- a/internal/services/iam/group_data_source.go +++ b/internal/services/iam/group_data_source.go @@ -68,6 +68,7 @@ func DataSourceIamGroupRead(ctx context.Context, d *schema.ResourceData, m inter } d.SetId(groupID.(string)) + err := d.Set("group_id", groupID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/iam/group_membership.go b/internal/services/iam/group_membership.go index 8e03f5957d..e3458dc407 100644 --- a/internal/services/iam/group_membership.go +++ b/internal/services/iam/group_membership.go @@ -69,6 +69,7 @@ func resourceIamGroupMembershipCreate(ctx context.Context, d *schema.ResourceDat func resourceIamGroupMembershipRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + groupID, userID, applicationID, err := ExpandGroupMembershipID(d.Id()) if err != nil { return diag.FromErr(err) @@ -88,6 +89,7 @@ func resourceIamGroupMembershipRead(ctx context.Context, d *schema.ResourceData, } foundInGroup := false + if userID != "" { for _, groupUserID := range group.UserIDs { if groupUserID == userID { @@ -121,6 +123,7 @@ func resourceIamGroupMembershipRead(ctx context.Context, d *schema.ResourceData, func resourceIamGroupMembershipDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + groupID, userID, applicationID, err := ExpandGroupMembershipID(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/services/iam/group_membership_test.go b/internal/services/iam/group_membership_test.go index a928f9220b..c92bdc0d88 100644 --- a/internal/services/iam/group_membership_test.go +++ b/internal/services/iam/group_membership_test.go @@ -153,6 +153,7 @@ func testAccCheckIamGroupMembershipApplicationInGroup(tt *acctest.TestTools, n s expectedApplicationID := appRS.Primary.ID api := iam.NewAPI(tt.Meta) + groupID, _, applicationID, err := iam.ExpandGroupMembershipID(rs.Primary.ID) if err != nil { return err @@ -200,6 +201,7 @@ func testAccCheckIamGroupMembershipUserInGroup(tt *acctest.TestTools, n string, expectedUserID := appRS.Primary.ID api := iam.NewAPI(tt.Meta) + groupID, userID, _, err := iam.ExpandGroupMembershipID(rs.Primary.ID) if err != nil { return err diff --git a/internal/services/iam/policy.go b/internal/services/iam/policy.go index 776f5bbb1e..473d21dd06 100644 --- a/internal/services/iam/policy.go +++ b/internal/services/iam/policy.go @@ -153,6 +153,7 @@ func resourceIamPolicyCreate(ctx context.Context, d *schema.ResourceData, m inte func resourceIamPolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + pol, err := api.GetPolicy(&iam.GetPolicyRequest{ PolicyID: d.Id(), }, scw.WithContext(ctx)) @@ -165,6 +166,7 @@ func resourceIamPolicyRead(ctx context.Context, d *schema.ResourceData, m interf return diag.FromErr(err) } + _ = d.Set("name", pol.Name) _ = d.Set("description", pol.Description) _ = d.Set("created_at", types.FlattenTime(pol.CreatedAt)) @@ -176,9 +178,11 @@ func resourceIamPolicyRead(ctx context.Context, d *schema.ResourceData, m interf if pol.UserID != nil { _ = d.Set("user_id", types.FlattenStringPtr(pol.UserID)) } + if pol.GroupID != nil { _ = d.Set("group_id", types.FlattenStringPtr(pol.GroupID)) } + if pol.ApplicationID != nil { _ = d.Set("application_id", types.FlattenStringPtr(pol.ApplicationID)) } @@ -210,30 +214,37 @@ func resourceIamPolicyUpdate(ctx context.Context, d *schema.ResourceData, m inte hasUpdated = true req.Name = types.ExpandStringPtr(d.Get("name")) } + if d.HasChange("description") { hasUpdated = true req.Description = types.ExpandUpdatedStringPtr(d.Get("description")) } + if d.HasChange("tags") { hasUpdated = true req.Tags = types.ExpandUpdatedStringsPtr(d.Get("tags")) } + if d.HasChange("user_id") { hasUpdated = true req.UserID = types.ExpandStringPtr(d.Get("user_id")) } + if d.HasChange("group_id") { hasUpdated = true req.GroupID = types.ExpandStringPtr(d.Get("group_id")) } + if d.HasChange("application_id") { hasUpdated = true req.ApplicationID = types.ExpandStringPtr(d.Get("application_id")) } + if noPrincipal := d.Get("no_principal"); d.HasChange("no_principal") && noPrincipal.(bool) { hasUpdated = true req.NoPrincipal = types.ExpandBoolPtr(noPrincipal) } + if hasUpdated { _, err := api.UpdatePolicy(req, scw.WithContext(ctx)) if err != nil { diff --git a/internal/services/iam/policy_test.go b/internal/services/iam/policy_test.go index 7b1ae8dbb6..d4366eedaf 100644 --- a/internal/services/iam/policy_test.go +++ b/internal/services/iam/policy_test.go @@ -17,6 +17,7 @@ import ( func TestAccPolicy_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeIAMManager(tt) require.NoError(t, err) @@ -90,6 +91,7 @@ func TestAccPolicy_Basic(t *testing.T) { func TestAccPolicy_NoUpdate(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeIAMManager(tt) require.NoError(t, err) @@ -156,6 +158,7 @@ func TestAccPolicy_ChangeLinkedEntity(t *testing.T) { ctx := context.Background() project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeIAMManager(tt) require.NoError(t, err) + randAppName := "tf-tests-scaleway-iam-app-policy-permissions" randGroupName := "tf-tests-scaleway-iam-group-policy-permissions" @@ -257,6 +260,7 @@ func TestAccPolicy_ChangeLinkedEntity(t *testing.T) { func TestAccPolicy_ChangePermissions(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeIAMManager(tt) require.NoError(t, err) @@ -347,6 +351,7 @@ func TestAccPolicy_ChangePermissions(t *testing.T) { func TestAccPolicy_ProjectID(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeIAMManager(tt) require.NoError(t, err) @@ -412,6 +417,7 @@ func TestAccPolicy_ProjectID(t *testing.T) { func TestAccPolicy_Condition(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeIAMManager(tt) require.NoError(t, err) @@ -506,6 +512,7 @@ func TestAccPolicy_Condition(t *testing.T) { func TestAccPolicy_ChangeRulePrincipal(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeIAMManager(tt) require.NoError(t, err) diff --git a/internal/services/iam/ssh_key_account_data_source_test.go b/internal/services/iam/ssh_key_account_data_source_test.go index 606edf5b5e..9b2aee9627 100644 --- a/internal/services/iam/ssh_key_account_data_source_test.go +++ b/internal/services/iam/ssh_key_account_data_source_test.go @@ -13,6 +13,7 @@ func TestAccDataSourceAccountSSHKey_Basic(t *testing.T) { dataSourceAccountSSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILHy/M5FVm5ydLGcal3e5LNcfTalbeN7QL/ZGCvDEdqJ foobar@example.com" dataSourceAccountSSHKeyWithoutComment := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILHy/M5FVm5ydLGcal3e5LNcfTalbeN7QL/ZGCvDEdqJ" sshKeyName := "TestAccScalewayDataSourceAccountSSHKey_Basic" + tt := acctest.NewTestTools(t) defer tt.Cleanup() resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/iam/ssh_key_account_test.go b/internal/services/iam/ssh_key_account_test.go index 95b1bae8e9..c19266606a 100644 --- a/internal/services/iam/ssh_key_account_test.go +++ b/internal/services/iam/ssh_key_account_test.go @@ -13,6 +13,7 @@ func TestAccSSHKeyAccount_basic(t *testing.T) { name := "tf-test-account-ssh-key-basic" SSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEEYrzDOZmhItdKaDAEqJQ4ORS2GyBMtBozYsK5kiXXX opensource@scaleway.com" FormattedSSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEEYrzDOZmhItdKaDAEqJQ4ORS2GyBMtBozYsK5kiXXX" + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -55,6 +56,7 @@ func TestAccSSHKeyAccount_WithNewLine(t *testing.T) { name := "tf-test-account-ssh-key-newline" SSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDjfkdWCwkYlVQMDUfiZlVrmjaGOfBYnmkucssae8Iup opensource@scaleway.com" FormattedSSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDjfkdWCwkYlVQMDUfiZlVrmjaGOfBYnmkucssae8Iup" + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -84,6 +86,7 @@ func TestAccSSHKeyAccount_ChangeResourceName(t *testing.T) { name := "TestAccScalewayAccountSSHKey_ChangeResourceName" SSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICJEoOOgQBLJPs4g/XcPTKT82NywNPpxeuA20FlOPlpO opensource@scaleway.com" FormattedSSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICJEoOOgQBLJPs4g/XcPTKT82NywNPpxeuA20FlOPlpO" + tt := acctest.NewTestTools(t) defer tt.Cleanup() diff --git a/internal/services/iam/ssh_key_data_source.go b/internal/services/iam/ssh_key_data_source.go index 8f6073cdd0..cf1fba6394 100644 --- a/internal/services/iam/ssh_key_data_source.go +++ b/internal/services/iam/ssh_key_data_source.go @@ -37,6 +37,7 @@ func DataSourceIamSSHKeyRead(ctx context.Context, d *schema.ResourceData, m inte sshKeyID, sshKeyIDExists := d.GetOk("ssh_key_id") if !sshKeyIDExists { sshKeyName := d.Get("name").(string) + res, err := iamAPI.ListSSHKeys(&iam.ListSSHKeysRequest{ Name: types.ExpandStringPtr(sshKeyName), ProjectID: types.ExpandStringPtr(d.Get("project_id")), diff --git a/internal/services/iam/ssh_key_data_source_test.go b/internal/services/iam/ssh_key_data_source_test.go index 3226591fea..977e3cacde 100644 --- a/internal/services/iam/ssh_key_data_source_test.go +++ b/internal/services/iam/ssh_key_data_source_test.go @@ -13,6 +13,7 @@ func TestAccDataSourceSSHKey_Basic(t *testing.T) { dataSourceIamSSHKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILHy/M5FVm5ydLGcal3e5LNcfTalbeN7QL/ZGCvDEdqJ foobar@example.com" dataSourceIamSSHKeyWithoutComment := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILHy/M5FVm5ydLGcal3e5LNcfTalbeN7QL/ZGCvDEdqJ" sshKeyName := "tf-test-ds-iam-ssh-key-basic" + tt := acctest.NewTestTools(t) defer tt.Cleanup() resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/iam/ssh_key_test.go b/internal/services/iam/ssh_key_test.go index 65607cca41..495f45e0ef 100644 --- a/internal/services/iam/ssh_key_test.go +++ b/internal/services/iam/ssh_key_test.go @@ -16,6 +16,7 @@ const ( func TestAccSSHKey_basic(t *testing.T) { name := "tf-test-iam-ssh-key-basic" + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -56,6 +57,7 @@ func TestAccSSHKey_basic(t *testing.T) { func TestAccSSHKey_WithNewLine(t *testing.T) { name := "tf-test-iam-ssh-key-newline" + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -83,6 +85,7 @@ func TestAccSSHKey_WithNewLine(t *testing.T) { func TestAccSSHKey_ChangeResourceName(t *testing.T) { name := "tf-test-iam-ssh-key-change-resource-name" + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -123,6 +126,7 @@ func TestAccSSHKey_ChangeResourceName(t *testing.T) { func TestAccSSHKey_Disabled(t *testing.T) { name := "tf-test-iam-ssh-key-disabled" + tt := acctest.NewTestTools(t) defer tt.Cleanup() diff --git a/internal/services/iam/testfuncs/sweep.go b/internal/services/iam/testfuncs/sweep.go index 94483e55a7..1a33ccc97d 100644 --- a/internal/services/iam/testfuncs/sweep.go +++ b/internal/services/iam/testfuncs/sweep.go @@ -53,10 +53,12 @@ func testSweepUser(_ string) error { if err != nil { return fmt.Errorf("failed to list users: %w", err) } + for _, user := range listUsers.Users { if !acctest.IsTestResource(user.Email) { continue } + err = api.DeleteUser(&iamSDK.DeleteUserRequest{ UserID: user.ID, }) @@ -84,6 +86,7 @@ func testSweepSSHKey(_ string) error { if !acctest.IsTestResource(sshKey.Name) { continue } + err := iamAPI.DeleteSSHKey(&iamSDK.DeleteSSHKeyRequest{ SSHKeyID: sshKey.ID, }) @@ -111,10 +114,12 @@ func testSweepIamPolicy(_ string) error { if err != nil { return fmt.Errorf("failed to list policies: %w", err) } + for _, pol := range listPols.Policies { if !acctest.IsTestResource(pol.Name) { continue } + err = api.DeletePolicy(&iamSDK.DeletePolicyRequest{ PolicyID: pol.ID, }) @@ -142,10 +147,12 @@ func testSweepIamGroup(_ string) error { if err != nil { return fmt.Errorf("failed to list groups: %w", err) } + for _, group := range listApps.Groups { if !acctest.IsTestResource(group.Name) { continue } + err = api.DeleteGroup(&iamSDK.DeleteGroupRequest{ GroupID: group.ID, }) @@ -173,6 +180,7 @@ func testSweepIamApplication(_ string) error { if err != nil { return fmt.Errorf("failed to list applications: %w", err) } + for _, app := range listApps.Applications { if !acctest.IsTestResource(app.Name) { continue @@ -207,10 +215,12 @@ func testSweepIamAPIKey(_ string) error { if err != nil { return fmt.Errorf("failed to list api keys: %w", err) } + for _, key := range listAPIKeys.APIKeys { if !acctest.IsTestResource(key.Description) { continue } + err = api.DeleteAPIKey(&iamSDK.DeleteAPIKeyRequest{ AccessKey: key.AccessKey, }) diff --git a/internal/services/iam/types.go b/internal/services/iam/types.go index 9fc654c7c4..a9ecfa9106 100644 --- a/internal/services/iam/types.go +++ b/internal/services/iam/types.go @@ -10,6 +10,7 @@ import ( func expandPermissionSetNames(rawPermissions interface{}) *[]string { permissions := []string{} permissionSet := rawPermissions.(*schema.Set) + for _, rawPermission := range permissionSet.List() { permissions = append(permissions, rawPermission.(string)) } @@ -30,6 +31,7 @@ func flattenPermissionSetNames(permissions []string) *schema.Set { func expandPolicyRuleSpecs(d interface{}) []*iam.RuleSpecs { rules := []*iam.RuleSpecs(nil) + rawRules := d.([]interface{}) for _, rawRule := range rawRules { mapRule := rawRule.(map[string]interface{}) @@ -37,12 +39,15 @@ func expandPolicyRuleSpecs(d interface{}) []*iam.RuleSpecs { PermissionSetNames: expandPermissionSetNames(mapRule["permission_set_names"]), Condition: mapRule["condition"].(string), } + if orgID, orgIDExists := mapRule["organization_id"]; orgIDExists && orgID.(string) != "" { rule.OrganizationID = scw.StringPtr(orgID.(string)) } + if projIDs, projIDsExists := mapRule["project_ids"]; projIDsExists { rule.ProjectIDs = types.ExpandStringsPtr(projIDs) } + rules = append(rules, rule) } @@ -51,6 +56,7 @@ func expandPolicyRuleSpecs(d interface{}) []*iam.RuleSpecs { func flattenPolicyRules(rules []*iam.Rule) interface{} { rawRules := []interface{}(nil) + for _, rule := range rules { rawRule := map[string]interface{}{} if rule.OrganizationID != nil { @@ -58,12 +64,15 @@ func flattenPolicyRules(rules []*iam.Rule) interface{} { } else { rawRule["organization_id"] = nil } + if rule.ProjectIDs != nil { rawRule["project_ids"] = types.FlattenSliceString(*rule.ProjectIDs) } + if rule.PermissionSetNames != nil { rawRule["permission_set_names"] = flattenPermissionSetNames(*rule.PermissionSetNames) } + if rule.Condition != "" { rawRule["condition"] = rule.Condition } diff --git a/internal/services/iam/user.go b/internal/services/iam/user.go index bbd0aaa293..28c1ba9f89 100644 --- a/internal/services/iam/user.go +++ b/internal/services/iam/user.go @@ -85,6 +85,7 @@ func ResourceUser() *schema.Resource { func resourceIamUserCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) email := d.Get("email").(string) + user, err := api.CreateUser(&iam.CreateUserRequest{ OrganizationID: d.Get("organization_id").(string), Email: &email, @@ -101,6 +102,7 @@ func resourceIamUserCreate(ctx context.Context, d *schema.ResourceData, m interf func resourceIamUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { api := NewAPI(m) + user, err := api.GetUser(&iam.GetUserRequest{ UserID: d.Id(), }, scw.WithContext(ctx)) diff --git a/internal/services/iam/user_data_source.go b/internal/services/iam/user_data_source.go index e2a8abc15f..8e744a73a7 100644 --- a/internal/services/iam/user_data_source.go +++ b/internal/services/iam/user_data_source.go @@ -53,16 +53,20 @@ func DataSourceIamUserRead(ctx context.Context, d *schema.ResourceData, m interf iamAPI := NewAPI(m) var email, organizationID string + var tags []string + userID, ok := d.GetOk("user_id") if ok { userID = d.Get("user_id") + res, err := iamAPI.GetUser(&iam.GetUserRequest{ UserID: userID.(string), }, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) } + email = res.Email organizationID = res.OrganizationID tags = res.Tags @@ -73,23 +77,28 @@ func DataSourceIamUserRead(ctx context.Context, d *schema.ResourceData, m interf if err != nil { return diag.FromErr(err) } + if len(res.Users) == 0 { return diag.FromErr(fmt.Errorf("no user found with the email address %s", d.Get("email"))) } + for _, user := range res.Users { if user.Email == d.Get("email").(string) { if userID != "" { return diag.Errorf("more than 1 user found with the same email %s", d.Get("email")) } + userID, email, tags = user.ID, user.Email, user.Tags } } + if userID == "" { return diag.Errorf("no user found with the email %s", d.Get("email")) } } d.SetId(userID.(string)) + err := d.Set("user_id", userID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/inference/deployment.go b/internal/services/inference/deployment.go index a4ac9995ca..fdfa390c77 100644 --- a/internal/services/inference/deployment.go +++ b/internal/services/inference/deployment.go @@ -274,7 +274,9 @@ func ResourceDeploymentRead(ctx context.Context, d *schema.ResourceData, m inter _ = d.Set("tags", types.ExpandUpdatedStringsPtr(deployment.Tags)) _ = d.Set("created_at", types.FlattenTime(deployment.CreatedAt)) _ = d.Set("updated_at", types.FlattenTime(deployment.UpdatedAt)) + var privateEndpoints []map[string]interface{} + var publicEndpoints []map[string]interface{} for _, endpoint := range deployment.Endpoints { @@ -287,6 +289,7 @@ func ResourceDeploymentRead(ctx context.Context, d *schema.ResourceData, m inter } privateEndpoints = append(privateEndpoints, privateEndpointSpec) } + if endpoint.PublicAccess != nil { publicEndpointSpec := map[string]interface{}{ "id": endpoint.ID, @@ -301,6 +304,7 @@ func ResourceDeploymentRead(ctx context.Context, d *schema.ResourceData, m inter if privateEndpoints != nil { _ = d.Set("private_endpoint", privateEndpoints) } + if publicEndpoints != nil { _ = d.Set("public_endpoint", publicEndpoints) } @@ -363,6 +367,7 @@ func ResourceDeploymentDelete(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + _, err = api.DeleteDeployment(&inference.DeleteDeploymentRequest{ Region: region, DeploymentID: id, @@ -370,6 +375,7 @@ func ResourceDeploymentDelete(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + _, err = waitForDeployment(ctx, api, region, id, d.Timeout(schema.TimeoutDelete)) if err != nil && !httperrors.Is404(err) { return diag.FromErr(err) diff --git a/internal/services/inference/testfuncs/sweep.go b/internal/services/inference/testfuncs/sweep.go index 9a0db8c004..d49be61b3e 100644 --- a/internal/services/inference/testfuncs/sweep.go +++ b/internal/services/inference/testfuncs/sweep.go @@ -21,7 +21,9 @@ func AddTestSweepers() { func testSweepDeployment(_ string) error { return acctest.SweepRegions((&inference.API{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { inferenceAPI := inference.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the inference deployments in (%s)", region) + listDeployments, err := inferenceAPI.ListDeployments( &inference.ListDeploymentsRequest{ Region: region, diff --git a/internal/services/instance/data_source_instance_private_nic.go b/internal/services/instance/data_source_instance_private_nic.go index a61e6d9544..903ebcf399 100644 --- a/internal/services/instance/data_source_instance_private_nic.go +++ b/internal/services/instance/data_source_instance_private_nic.go @@ -48,7 +48,9 @@ func DataSourceInstancePrivateNICRead(ctx context.Context, d *schema.ResourceDat serverID := locality.ExpandID(d.Get("server_id")) id, ok := d.GetOk("private_nic_id") + var privateNICID string + if !ok { resp, err := instanceAPI.ListPrivateNICs(&instance.ListPrivateNICsRequest{ Zone: zone, @@ -75,6 +77,7 @@ func DataSourceInstancePrivateNICRead(ctx context.Context, d *schema.ResourceDat privateNICID, ) d.SetId(zonedID) + err = d.Set("private_nic_id", zonedID) if err != nil { return diag.FromErr(err) @@ -113,6 +116,7 @@ func privateNICWithFilters(privateNICs []*instance.PrivateNIC, d *schema.Resourc if privateNIC != nil { return nil, fmt.Errorf("found more than one private nic for request private network (%s)", privateNetworkID) } + privateNIC = pnic } } diff --git a/internal/services/instance/helpers_instance.go b/internal/services/instance/helpers_instance.go index ee2c78af86..979aa558cd 100644 --- a/internal/services/instance/helpers_instance.go +++ b/internal/services/instance/helpers_instance.go @@ -89,6 +89,7 @@ func orderVolumes(v map[string]*instance.Volume) []*instance.Volume { for index := range v { indexes = append(indexes, index) } + sort.Strings(indexes) orderedVolumes := make([]*instance.Volume, 0, len(indexes)) @@ -105,6 +106,7 @@ func sortVolumeServer(v map[string]*instance.VolumeServer) []*instance.VolumeSer for index := range v { indexes = append(indexes, index) } + sort.Strings(indexes) sortedVolumes := make([]*instance.VolumeServer, 0, len(indexes)) @@ -154,6 +156,7 @@ func reachState(ctx context.Context, api *BlockAndInstanceAPI, zone scw.Zone, se if err != nil { return err } + fromState := response.Server.State if response.Server.State == toState { @@ -236,6 +239,7 @@ func getServerType(ctx context.Context, apiInstance *instance.API, zone scw.Zone func validateLocalVolumeSizes(volumes map[string]*instance.VolumeServerTemplate, serverType *instance.ServerType, commercialType string) error { // Calculate local volume total size. var localVolumeTotalSize scw.Size + for _, volume := range volumes { if volume.VolumeType == instance.VolumeVolumeTypeLSSD && volume.Size != nil { localVolumeTotalSize += *volume.Size @@ -277,11 +281,13 @@ func preparePrivateNIC( r := pn.(map[string]interface{}) zonedID, pnExist := r["pn_id"] privateNetworkID := locality.ExpandID(zonedID.(string)) + if pnExist { region, err := server.Zone.Region() if err != nil { return nil, err } + currentPN, err := vpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{ PrivateNetworkID: locality.ExpandID(privateNetworkID), Region: region, @@ -289,6 +295,7 @@ func preparePrivateNIC( if err != nil { return nil, err } + query := &instance.CreatePrivateNICRequest{ Zone: server.Zone, ServerID: server.ID, @@ -387,13 +394,16 @@ func (ph *privateNICsHandler) attach(ctx context.Context, n interface{}, timeout func (ph *privateNICsHandler) set(d *schema.ResourceData) error { raw := d.Get("private_network") privateNetworks := []map[string]interface{}(nil) + for index := range raw.([]interface{}) { pnKey := fmt.Sprintf("private_network.%d.pn_id", index) keyValue := d.Get(pnKey) + keyRaw, err := ph.get(keyValue.(string)) if err != nil { continue } + privateNetworks = append(privateNetworks, keyRaw.(map[string]interface{})) } @@ -405,6 +415,7 @@ func (ph *privateNICsHandler) get(key string) (interface{}, error) { if err != nil { return nil, err } + pn, ok := ph.privateNICsMap[id] if !ok { return nil, fmt.Errorf("could not find private network ID %s on locality %s", key, loc) diff --git a/internal/services/instance/helpers_instance_block.go b/internal/services/instance/helpers_instance_block.go index 0c7cc38b95..9f370e7840 100644 --- a/internal/services/instance/helpers_instance_block.go +++ b/internal/services/instance/helpers_instance_block.go @@ -101,6 +101,7 @@ func (api *BlockAndInstanceAPI) GetUnknownVolume(req *GetUnknownVolumeRequest, o VolumeID: req.VolumeID, }, opts...) notFoundErr := &scw.ResourceNotFoundError{} + if err != nil && !errors.As(err, ¬FoundErr) { return nil, err } @@ -138,6 +139,7 @@ func (api *BlockAndInstanceAPI) GetUnknownVolume(req *GetUnknownVolumeRequest, o if blockVolume.Specs != nil { vol.Iops = blockVolume.Specs.PerfIops } + for _, ref := range blockVolume.References { if ref.ProductResourceType == "instance_server" { vol.ServerID = &ref.ProductResourceID @@ -208,6 +210,7 @@ func (api *BlockAndInstanceAPI) GetUnknownSnapshot(req *GetUnknownSnapshotReques SnapshotID: req.SnapshotID, }, opts...) notFoundErr := &scw.ResourceNotFoundError{} + if err != nil && !errors.As(err, ¬FoundErr) { return nil, err } diff --git a/internal/services/instance/image.go b/internal/services/instance/image.go index 0ef18d8c7a..693a70dfd0 100644 --- a/internal/services/instance/image.go +++ b/internal/services/instance/image.go @@ -195,10 +195,12 @@ func ResourceInstanceImageCreate(ctx context.Context, d *schema.ResourceData, m if volumesExist { req.ExtraVolumes = expandImageExtraVolumesTemplates(locality.ExpandIDs(extraVolumesIDs)) } + tags, tagsExist := d.GetOk("tags") if tagsExist { req.Tags = types.ExpandStrings(tags) } + if _, exist := d.GetOk("public"); exist { req.Public = types.ExpandBoolPtr(types.GetBool(d, "public")) } @@ -274,12 +276,15 @@ func ResourceInstanceImageUpdate(ctx context.Context, d *schema.ResourceData, m if d.HasChange("name") { req.Name = types.ExpandStringPtr(d.Get("name")) } + if d.HasChange("architecture") { req.Arch = instanceSDK.Arch(d.Get("architecture").(string)) } + if d.HasChange("public") { req.Public = types.ExpandBoolPtr(types.GetBool(d, "public")) } + req.Tags = types.ExpandUpdatedStringsPtr(d.Get("tags")) image, err := api.GetImage(&instanceSDK.GetImageRequest{ @@ -299,6 +304,7 @@ func ResourceInstanceImageUpdate(ctx context.Context, d *schema.ResourceData, m ID: vol.ID, } } + req.ExtraVolumes = volTemplate } @@ -306,6 +312,7 @@ func ResourceInstanceImageUpdate(ctx context.Context, d *schema.ResourceData, m if req.Name == nil { req.Name = &image.Image.Name } + if req.Arch == "" { req.Arch = image.Image.Arch } diff --git a/internal/services/instance/image_data_source.go b/internal/services/instance/image_data_source.go index 8336e6eaa1..56560d25bf 100644 --- a/internal/services/instance/image_data_source.go +++ b/internal/services/instance/image_data_source.go @@ -114,7 +114,9 @@ func DataSourceInstanceImageRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + var matchingImages []*instance.Image + for _, image := range res.Images { if image.Name == d.Get("name").(string) { matchingImages = append(matchingImages, image) @@ -124,6 +126,7 @@ func DataSourceInstanceImageRead(ctx context.Context, d *schema.ResourceData, m if len(matchingImages) == 0 { return diag.FromErr(fmt.Errorf("no image found with the name %s and architecture %s in zone %s", d.Get("name"), d.Get("architecture"), zone)) } + if len(matchingImages) > 1 && !d.Get("latest").(bool) { return diag.FromErr(fmt.Errorf("%d images found with the same name %s and architecture %s in zone %s", len(matchingImages), d.Get("name"), d.Get("architecture"), zone)) } @@ -131,6 +134,7 @@ func DataSourceInstanceImageRead(ctx context.Context, d *schema.ResourceData, m sort.Slice(matchingImages, func(i, j int) bool { return matchingImages[i].ModificationDate.After(*matchingImages[j].ModificationDate) }) + for _, image := range matchingImages { if image.Name == d.Get("name").(string) { imageID = image.ID @@ -138,7 +142,6 @@ func DataSourceInstanceImageRead(ctx context.Context, d *schema.ResourceData, m break } } - // imageID will always be set here } zonedID := datasource.NewZonedID(imageID, zone) @@ -183,6 +186,7 @@ func DataSourceInstanceImageRead(ctx context.Context, d *schema.ResourceData, m for _, volume := range orderVolumes(resp.Image.ExtraVolumes) { additionalVolumeIDs = append(additionalVolumeIDs, volume.ID) } + _ = d.Set("additional_volume_ids", additionalVolumeIDs) return nil diff --git a/internal/services/instance/image_test.go b/internal/services/instance/image_test.go index f4d4ca5019..4f3f945aac 100644 --- a/internal/services/instance/image_test.go +++ b/internal/services/instance/image_test.go @@ -548,10 +548,12 @@ func isImageDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { if rs.Type != "scaleway_instance_image" { continue } + instanceAPI, zone, ID, err := instance.NewAPIWithZoneAndID(tt.Meta, rs.Primary.ID) if err != nil { return err } + _, err = instanceAPI.GetImage(&instanceSDK.GetImageRequest{ ImageID: ID, Zone: zone, diff --git a/internal/services/instance/ip.go b/internal/services/instance/ip.go index aeba6851d6..66c7a6a7f9 100644 --- a/internal/services/instance/ip.go +++ b/internal/services/instance/ip.go @@ -76,15 +76,18 @@ func ResourceInstanceIPCreate(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + req := &instanceSDK.CreateIPRequest{ Zone: zone, Project: types.ExpandStringPtr(d.Get("project_id")), Type: instanceSDK.IPType(d.Get("type").(string)), } tags := types.ExpandStrings(d.Get("tags")) + if len(tags) > 0 { req.Tags = tags } + res, err := instanceAPI.CreateIP(req, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -98,6 +101,7 @@ func ResourceInstanceIPCreate(ctx context.Context, d *schema.ResourceData, m int Reverse: &instanceSDK.NullableStringValue{Value: *reverseStrPtr}, Zone: zone, } + _, err = instanceAPI.UpdateIP(req, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -114,6 +118,7 @@ func ResourceInstanceIPUpdate(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + req := &instanceSDK.UpdateIPRequest{ IP: ID, Zone: zone, @@ -153,12 +158,14 @@ func ResourceInstanceIPRead(ctx context.Context, d *schema.ResourceData, m inter } address := res.IP.Address.String() + prefix := res.IP.Prefix.String() if prefix == types.NetIPNil { ipnet := scw.IPNet{} _ = (&ipnet).UnmarshalJSON([]byte("\"" + res.IP.Address.String() + "\"")) prefix = ipnet.String() } + if address == types.NetIPNil { address = res.IP.Prefix.IP.String() } diff --git a/internal/services/instance/ip_data_source.go b/internal/services/instance/ip_data_source.go index aa5bce82d2..a522f7c8ba 100644 --- a/internal/services/instance/ip_data_source.go +++ b/internal/services/instance/ip_data_source.go @@ -48,7 +48,9 @@ func DataSourceInstanceIPRead(ctx context.Context, d *schema.ResourceData, m int } id, ok := d.GetOk("id") + var ID string + if !ok { res, err := instanceAPI.GetIP(&instance.GetIPRequest{ IP: d.Get("address").(string), @@ -64,10 +66,12 @@ func DataSourceInstanceIPRead(ctx context.Context, d *schema.ResourceData, m int return diag.FromErr(err) } + ID = res.IP.ID } else { _, ID, _ = locality.ParseLocalizedID(id.(string)) } + d.SetId(zonal.NewIDString(zone, ID)) return ResourceInstanceIPRead(ctx, d, m) diff --git a/internal/services/instance/ip_reverse_dns.go b/internal/services/instance/ip_reverse_dns.go index baf27dc363..100cfc0b22 100644 --- a/internal/services/instance/ip_reverse_dns.go +++ b/internal/services/instance/ip_reverse_dns.go @@ -60,6 +60,7 @@ func ResourceInstanceIPReverseDNSCreate(ctx context.Context, d *schema.ResourceD if err != nil { return diag.FromErr(err) } + d.SetId(zonal.NewIDString(zone, res.IP.ID)) if _, ok := d.GetOk("reverse"); ok { @@ -131,6 +132,7 @@ func ResourceInstanceIPReverseDNSUpdate(ctx context.Context, d *schema.ResourceD } else { updateReverseReq.Reverse = &instanceSDK.NullableStringValue{Null: true} } + err := retryUpdateReverseDNS(ctx, instanceAPI, updateReverseReq, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) @@ -152,6 +154,7 @@ func ResourceInstanceIPReverseDNSDelete(ctx context.Context, d *schema.ResourceD IP: ID, Reverse: &instanceSDK.NullableStringValue{Null: true}, } + _, err = instanceAPI.UpdateIP(updateReverseReq, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/instance/ip_reverse_dns_test.go b/internal/services/instance/ip_reverse_dns_test.go index df262a157a..0796daadd3 100644 --- a/internal/services/instance/ip_reverse_dns_test.go +++ b/internal/services/instance/ip_reverse_dns_test.go @@ -12,6 +12,7 @@ import ( func TestAccIPReverseDns_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testDNSZone := "tf-reverse-instance." + acctest.TestDomain resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/instance/ip_test.go b/internal/services/instance/ip_test.go index 0679b67110..078d206f42 100644 --- a/internal/services/instance/ip_test.go +++ b/internal/services/instance/ip_test.go @@ -136,6 +136,7 @@ func isIPCIDRValid(name string, key string) resource.TestCheckFunc { if !exists { return fmt.Errorf("requested attribute %s[%q] does not exist", name, key) } + _, _, err := net.ParseCIDR(cidr) if err != nil { return fmt.Errorf("invalid cidr (%s) in %s[%q]", cidr, name, key) @@ -172,6 +173,7 @@ func isIPAttachedToServer(tt *acctest.TestTools, ipResource, serverResource stri if !ok { return fmt.Errorf("resource not found: %s", ipResource) } + serverState, ok := s.RootModule().Resources[serverResource] if !ok { return fmt.Errorf("resource not found: %s", serverResource) diff --git a/internal/services/instance/placement_group.go b/internal/services/instance/placement_group.go index 1b658b4228..91cf80a98e 100644 --- a/internal/services/instance/placement_group.go +++ b/internal/services/instance/placement_group.go @@ -128,6 +128,7 @@ func ResourceInstancePlacementGroupUpdate(ctx context.Context, d *schema.Resourc if err != nil { return diag.FromErr(err) } + req := &instanceSDK.UpdatePlacementGroupRequest{ Zone: zone, PlacementGroupID: ID, diff --git a/internal/services/instance/placement_group_data_source.go b/internal/services/instance/placement_group_data_source.go index 022649c3a1..02497d9b40 100644 --- a/internal/services/instance/placement_group_data_source.go +++ b/internal/services/instance/placement_group_data_source.go @@ -54,9 +54,11 @@ func DataSourcePlacementGroupRead(ctx context.Context, d *schema.ResourceData, m if placementGroupID != "" { return diag.Errorf("more than 1 placement group found with the same name %s", d.Get("name")) } + placementGroupID = placementGroup.ID } } + if placementGroupID == "" { return diag.Errorf("no placementgroup found with the name %s", d.Get("name")) } @@ -64,6 +66,7 @@ func DataSourcePlacementGroupRead(ctx context.Context, d *schema.ResourceData, m zoneID := datasource.NewZonedID(placementGroupID, zone) d.SetId(zoneID) + err = d.Set("placement_group_id", zoneID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/instance/private_nic.go b/internal/services/instance/private_nic.go index 3c045bd975..d07833bb1b 100644 --- a/internal/services/instance/private_nic.go +++ b/internal/services/instance/private_nic.go @@ -132,6 +132,7 @@ func ResourceInstancePrivateNICRead(ctx context.Context, d *schema.ResourceData, if err != nil { return diag.FromErr(err) } + zone, privateNICID, serverID, err := zonal.ParseNestedID(d.Id()) if err != nil { return diag.FromErr(err) @@ -199,6 +200,7 @@ func ResourceInstancePrivateNICDelete(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(err) } + zone, privateNICID, serverID, err := zonal.ParseNestedID(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/services/instance/security_group.go b/internal/services/instance/security_group.go index f859a546c5..de07572ac2 100644 --- a/internal/services/instance/security_group.go +++ b/internal/services/instance/security_group.go @@ -120,9 +120,11 @@ func ResourceInstanceSecurityGroupCreate(ctx context.Context, d *schema.Resource EnableDefaultSecurity: types.ExpandBoolPtr(d.Get("enable_default_security")), } tags := types.ExpandStrings(d.Get("tags")) + if len(tags) > 0 { req.Tags = tags } + res, err := instanceAPI.CreateSecurityGroup(req, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -173,6 +175,7 @@ func ResourceInstanceSecurityGroupRead(ctx context.Context, d *schema.ResourceDa if err != nil { return diag.FromErr(err) } + _ = d.Set("inbound_rule", inboundRules) _ = d.Set("outbound_rule", outboundRules) } @@ -188,9 +191,11 @@ func getSecurityGroupRules(ctx context.Context, instanceAPI *instanceSDK.API, zo if err != nil { return nil, nil, err } + sort.Slice(resRules.Rules, func(i, j int) bool { return resRules.Rules[i].Position < resRules.Rules[j].Position }) + apiRules := map[instanceSDK.SecurityGroupRuleDirection][]*instanceSDK.SecurityGroupRule{ instanceSDK.SecurityGroupRuleDirectionInbound: {}, instanceSDK.SecurityGroupRuleDirectionOutbound: {}, @@ -205,6 +210,7 @@ func getSecurityGroupRules(ctx context.Context, instanceAPI *instanceSDK.API, zo if !apiRule.Editable { continue } + apiRules[apiRule.Direction] = append(apiRules[apiRule.Direction], apiRule) } @@ -216,6 +222,7 @@ func getSecurityGroupRules(ctx context.Context, instanceAPI *instanceSDK.API, zo if errGroup != nil { return nil, nil, errGroup } + if ok, _ := SecurityGroupRuleEquals(stateRule, apiRule); !ok { stateRules[direction][index], err = securityGroupRuleFlatten(apiRule) if err != nil { @@ -227,6 +234,7 @@ func getSecurityGroupRules(ctx context.Context, instanceAPI *instanceSDK.API, zo if err != nil { return nil, nil, err } + stateRules[direction] = append(stateRules[direction], rulesGroup) } } @@ -245,6 +253,7 @@ func ResourceInstanceSecurityGroupUpdate(ctx context.Context, d *schema.Resource if err != nil { return diag.FromErr(err) } + zone, ID, err := zonal.ParseID(d.Id()) if err != nil { return diag.FromErr(err) @@ -254,6 +263,7 @@ func ResourceInstanceSecurityGroupUpdate(ctx context.Context, d *schema.Resource if d.Get("inbound_default_policy") != nil { inboundDefaultPolicy = instanceSDK.SecurityGroupPolicy(d.Get("inbound_default_policy").(string)) } + outboundDefaultPolicy := instanceSDK.SecurityGroupPolicy("") if d.Get("outbound_default_policy") != nil { outboundDefaultPolicy = instanceSDK.SecurityGroupPolicy(d.Get("outbound_default_policy").(string)) @@ -263,6 +273,7 @@ func ResourceInstanceSecurityGroupUpdate(ctx context.Context, d *schema.Resource if d.Get("description") != nil { description = d.Get("description").(string) } + updateReq := &instanceSDK.UpdateSecurityGroupRequest{ Zone: zone, SecurityGroupID: ID, @@ -310,6 +321,7 @@ func updateSecurityGroupeRules(ctx context.Context, d *schema.ResourceData, zone } setGroupRules := []*instanceSDK.SetSecurityGroupRulesRequestRule{} + for direction := range stateRules { // Loop for all state rules in this direction for _, rawStateRule := range stateRules[direction] { @@ -348,6 +360,7 @@ func ResourceInstanceSecurityGroupDelete(ctx context.Context, d *schema.Resource if err != nil { return diag.FromErr(err) } + zone, ID, err := zonal.ParseID(d.Id()) if err != nil { return diag.FromErr(err) @@ -424,10 +437,12 @@ func securityGroupRuleExpand(i interface{}) (*instanceSDK.SecurityGroupRule, err } action, _ := rawRule["action"].(string) + ipRange := rawRule["ip_range"].(string) if ipRange == "" { ipRange = rawRule["ip"].(string) + "/32" } + if ipRange == "/32" { ipRange = "0.0.0.0/0" } @@ -436,6 +451,7 @@ func securityGroupRuleExpand(i interface{}) (*instanceSDK.SecurityGroupRule, err if err != nil { return nil, err } + rule := &instanceSDK.SecurityGroupRule{ DestPortFrom: &portFrom, DestPortTo: &portTo, @@ -473,6 +489,7 @@ func securityGroupRuleFlatten(rule *instanceSDK.SecurityGroupRule) (map[string]i if err != nil { return nil, err } + res := map[string]interface{}{ "protocol": rule.Protocol.String(), "ip_range": ipnetRange, @@ -494,14 +511,17 @@ func SecurityGroupRuleEquals(ruleA, ruleB *instanceSDK.SecurityGroupRule) (bool, } portFromEqual := zeroIfNil(ruleA.DestPortFrom) == zeroIfNil(ruleB.DestPortFrom) portToEqual := zeroIfNil(ruleA.DestPortTo) == zeroIfNil(ruleB.DestPortTo) + ipRangeA, err := types.FlattenIPNet(ruleA.IPRange) if err != nil { return false, err } + ipRangeB, err := types.FlattenIPNet(ruleB.IPRange) if err != nil { return false, err } + ipEqual := ipRangeA == ipRangeB return ruleA.Action == ruleB.Action && diff --git a/internal/services/instance/security_group_data_source.go b/internal/services/instance/security_group_data_source.go index 4bb7b5a876..2befd3e23f 100644 --- a/internal/services/instance/security_group_data_source.go +++ b/internal/services/instance/security_group_data_source.go @@ -44,6 +44,7 @@ func DataSourceInstanceSecurityGroupRead(ctx context.Context, d *schema.Resource securityGroupID, ok := d.GetOk("security_group_id") if !ok { sgName := d.Get("name").(string) + res, err := instanceAPI.ListSecurityGroups(&instance.ListSecurityGroupsRequest{ Zone: zone, Name: types.ExpandStringPtr(sgName), diff --git a/internal/services/instance/security_group_data_source_test.go b/internal/services/instance/security_group_data_source_test.go index 8d391365d3..135e377132 100644 --- a/internal/services/instance/security_group_data_source_test.go +++ b/internal/services/instance/security_group_data_source_test.go @@ -11,6 +11,7 @@ import ( func TestAccDataSourceSecurityGroup_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + securityGroupName := "tf-security-group" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/instance/security_group_rules.go b/internal/services/instance/security_group_rules.go index 1f51fab733..7d6d9b0c98 100644 --- a/internal/services/instance/security_group_rules.go +++ b/internal/services/instance/security_group_rules.go @@ -81,6 +81,7 @@ func ResourceInstanceSecurityGroupRulesRead(ctx context.Context, d *schema.Resou func ResourceInstanceSecurityGroupRulesUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { securityGroupZonedID := d.Id() + instanceAPI, zone, securityGroupID, err := NewAPIWithZoneAndID(m, securityGroupZonedID) if err != nil { return diag.FromErr(err) @@ -96,6 +97,7 @@ func ResourceInstanceSecurityGroupRulesUpdate(ctx context.Context, d *schema.Res func ResourceInstanceSecurityGroupRulesDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { securityGroupZonedID := d.Id() + instanceAPI, zone, securityGroupID, err := NewAPIWithZoneAndID(m, securityGroupZonedID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/instance/security_group_rules_test.go b/internal/services/instance/security_group_rules_test.go index 9de3550d90..13487385dd 100644 --- a/internal/services/instance/security_group_rules_test.go +++ b/internal/services/instance/security_group_rules_test.go @@ -138,6 +138,7 @@ func TestAccSecurityGroupRules_Basic(t *testing.T) { func TestAccSecurityGroupRules_IPRanges(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + config := ` resource scaleway_instance_security_group sg01 { external_rules = true @@ -209,6 +210,7 @@ func TestAccSecurityGroupRules_IPRanges(t *testing.T) { func TestAccSecurityGroupRules_Basic2(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + config := ` resource scaleway_instance_security_group sg01 { external_rules = true diff --git a/internal/services/instance/security_group_test.go b/internal/services/instance/security_group_test.go index 8a90fbc076..d95992cb5e 100644 --- a/internal/services/instance/security_group_test.go +++ b/internal/services/instance/security_group_test.go @@ -20,6 +20,7 @@ import ( func TestAccSecurityGroup_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ipnetZero, err := types.ExpandIPNet("0.0.0.0/0") require.NoError(t, err) ipnetOne, err := types.ExpandIPNet("1.1.1.1") @@ -285,6 +286,7 @@ func TestAccSecurityGroup_ANY(t *testing.T) { func TestAccSecurityGroup_WithNoPort(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ipnetZero, err := types.ExpandIPNet("0.0.0.0/0") require.NoError(t, err) resource.ParallelTest(t, resource.TestCase{ @@ -320,6 +322,7 @@ func TestAccSecurityGroup_WithNoPort(t *testing.T) { func TestAccSecurityGroup_RemovePort(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ipnetZero, err := types.ExpandIPNet("0.0.0.0/0") require.NoError(t, err) resource.ParallelTest(t, resource.TestCase{ @@ -505,9 +508,11 @@ func securityGroupRuleIs(tt *acctest.TestTools, name string, direction instanceS if err != nil { return err } + sort.Slice(resRules.Rules, func(i, j int) bool { return resRules.Rules[i].Position < resRules.Rules[j].Position }) + apiRules := map[instanceSDK.SecurityGroupRuleDirection][]*instanceSDK.SecurityGroupRule{ instanceSDK.SecurityGroupRuleDirectionInbound: {}, instanceSDK.SecurityGroupRuleDirectionOutbound: {}, @@ -517,6 +522,7 @@ func securityGroupRuleIs(tt *acctest.TestTools, name string, direction instanceS if apiRule.Editable == false { continue } + apiRules[apiRule.Direction] = append(apiRules[apiRule.Direction], apiRule) } @@ -538,6 +544,7 @@ func isSecurityGroupPresent(tt *acctest.TestTools, n string) resource.TestCheckF } instanceAPI := instanceSDK.NewAPI(tt.Meta.ScwClient()) + _, err = instanceAPI.GetSecurityGroup(&instanceSDK.GetSecurityGroupRequest{ SecurityGroupID: ID, Zone: zone, @@ -553,6 +560,7 @@ func isSecurityGroupPresent(tt *acctest.TestTools, n string) resource.TestCheckF func isSecurityGroupDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { return func(state *terraform.State) error { instanceAPI := instanceSDK.NewAPI(tt.Meta.ScwClient()) + for _, rs := range state.RootModule().Resources { if rs.Type != "scaleway_instance_security_group" { continue diff --git a/internal/services/instance/server.go b/internal/services/instance/server.go index 31482d1cfe..6c01988287 100644 --- a/internal/services/instance/server.go +++ b/internal/services/instance/server.go @@ -418,6 +418,7 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m rootVolume := d.Get("root_volume.0").(map[string]any) req.Volumes["0"] = prepareRootVolume(rootVolume, serverType, imageUUID).VolumeTemplate() + if raw, ok := d.GetOk("additional_volume_ids"); ok { for i, volumeID := range raw.([]interface{}) { // We have to get the volume to know whether it is a local or a block volume @@ -425,6 +426,7 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(fmt.Errorf("failed to get additional volume: %w", err)) } + req.Volumes[strconv.Itoa(i+1)] = volumeTemplate } } @@ -439,6 +441,7 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m imageLabel := formatImageLabel(imageUUID) marketPlaceAPI := marketplace.NewAPI(meta.ExtractScwClient(m)) + image, err := marketPlaceAPI.GetLocalImageByLabel(&marketplace.GetLocalImageByLabelRequest{ CommercialType: commercialType, Zone: zone, @@ -448,6 +451,7 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(fmt.Errorf("could not get image '%s': %s", zonal.NewID(zone, imageLabel), err)) } + imageUUID = image.ID } @@ -515,6 +519,7 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + err = reachState(ctx, api, zone, res.Server.ID, targetState) if err != nil { return diag.FromErr(err) @@ -528,6 +533,7 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + pnRequest, err := preparePrivateNIC(ctx, rawPNICs, res.Server, vpcAPI) if err != nil { return diag.FromErr(err) @@ -543,6 +549,7 @@ func ResourceInstanceServerCreate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + tflog.Debug(ctx, fmt.Sprintf("private network created (ID: %s, status: %s)", pn.PrivateNic.ID, pn.PrivateNic.State)) _, err = waitForPrivateNIC(ctx, api.API, zone, res.Server.ID, pn.PrivateNic.ID, d.Timeout(schema.TimeoutCreate)) @@ -591,6 +598,7 @@ func ResourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m i if err != nil { return diag.FromErr(err) } + _ = d.Set("state", state) _ = d.Set("zone", string(zone)) _ = d.Set("name", server.Name) @@ -600,6 +608,7 @@ func ResourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m i if len(server.Tags) > 0 { _ = d.Set("tags", server.Tags) } + _ = d.Set("security_group_id", zonal.NewID(zone, server.SecurityGroup.ID).String()) // EnableIPv6 is deprecated _ = d.Set("enable_ipv6", server.EnableIPv6) //nolint:staticcheck @@ -658,10 +667,12 @@ func ResourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m i if server.IPv6 != nil { //nolint:staticcheck _ = d.Set("ipv6_address", server.IPv6.Address.String()) //nolint:staticcheck _ = d.Set("ipv6_gateway", server.IPv6.Gateway.String()) //nolint:staticcheck - prefixLength, err := strconv.Atoi(server.IPv6.Netmask) //nolint:staticcheck + + prefixLength, err := strconv.Atoi(server.IPv6.Netmask) //nolint:staticcheck if err != nil { return diag.FromErr(err) } + _ = d.Set("ipv6_prefix_length", prefixLength) } else { _ = d.Set("ipv6_address", nil) @@ -670,6 +681,7 @@ func ResourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m i } var additionalVolumesIDs []string + for i, serverVolume := range sortVolumeServer(server.Volumes) { if i == 0 { rootVolume := map[string]interface{}{} @@ -693,9 +705,11 @@ func ResourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m i } else if serverVolume.Size != nil { rootVolume["size_in_gb"] = int(uint64(*serverVolume.Size) / gb) } + if vol.IsBlockVolume() { rootVolume["sbs_iops"] = types.FlattenUint32Ptr(vol.Iops) } + _, rootVolumeAttributeSet := d.GetOk("root_volume") // Related to https://github.com/hashicorp/terraform-plugin-sdk/issues/142 rootVolume["delete_on_termination"] = d.Get("root_volume.0.delete_on_termination").(bool) || !rootVolumeAttributeSet rootVolume["volume_type"] = serverVolume.VolumeType @@ -721,17 +735,16 @@ func ResourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m i }, scw.WithContext(ctx)) userData := make(map[string]interface{}) + for key, value := range allUserData.UserData { userDataValue, err := io.ReadAll(value) if err != nil { return diag.FromErr(err) } - // if key != "cloud-init" { + userData[key] = string(userDataValue) - // } else { - // _ = d.Set("cloud_init", string(userDataValue)) - // } } + _ = d.Set("user_data", userData) //// @@ -812,12 +825,14 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + serverShouldUpdate = true updateRequest.Volumes = &volumes } if d.HasChange("placement_group_id") { serverShouldUpdate = true + placementGroupID := zonal.ExpandID(d.Get("placement_group_id")).ID if placementGroupID == "" { updateRequest.PlacementGroup = &instanceSDK.NullableStringValue{Null: true} @@ -825,6 +840,7 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m if !isStopped { return diag.FromErr(errors.New("instanceSDK must be stopped to change placement group")) } + updateRequest.PlacementGroup = &instanceSDK.NullableStringValue{Value: placementGroupID} } } @@ -889,6 +905,7 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m bootType := instanceSDK.BootType(d.Get("boot_type").(string)) serverShouldUpdate = true updateRequest.BootType = &bootType + if !isStopped { warnings = append(warnings, diag.Diagnostic{ Severity: diag.Warning, @@ -912,6 +929,7 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m for key, value := range userDataMap { userDataRequests.UserData[key] = bytes.NewBufferString(value.(string)) } + if !isStopped && d.HasChange("user_data.cloud-init") { warnings = append(warnings, diag.Diagnostic{ Severity: diag.Warning, @@ -939,6 +957,7 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { diag.FromErr(err) } + if raw, ok := d.GetOk("private_network"); ok { // retrieve all current private network interfaces for index := range raw.([]interface{}) { @@ -955,6 +974,7 @@ func ResourceInstanceServerUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + err = ph.attach(ctx, n, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) @@ -1055,6 +1075,7 @@ func ResourceInstanceServerDelete(ctx context.Context, d *schema.ResourceData, m if httperrors.Is404(err) { return nil } + if err != nil { return diag.FromErr(err) } @@ -1069,6 +1090,7 @@ func ResourceInstanceServerDelete(ctx context.Context, d *schema.ResourceData, m for index := range raw.([]interface{}) { pnKey := fmt.Sprintf("private_network.%d.pn_id", index) pn := d.Get(pnKey) + err := ph.detach(ctx, pn, d.Timeout(schema.TimeoutDelete)) if err != nil { return diag.FromErr(err) @@ -1101,6 +1123,7 @@ func ResourceInstanceServerDelete(ctx context.Context, d *schema.ResourceData, m if !volumeExist { return diag.Errorf("volume ID not found") } + err = api.DeleteUnknownVolume(&DeleteUnknownVolumeRequest{ Zone: zone, VolumeID: locality.ExpandID(volumeID), @@ -1212,6 +1235,7 @@ func customDiffInstanceServerImage(ctx context.Context, diff *schema.ResourceDif if err != nil { return err } + server, err := instanceAPI.GetServer(&instanceSDK.GetServerRequest{ Zone: zone, ServerID: id, @@ -1236,9 +1260,11 @@ func customDiffInstanceServerImage(ctx context.Context, diff *schema.ResourceDif // If image is a label, we check that server.Image.ID matches the label in case the user has edited // the image with another tool. marketplaceAPI := marketplace.NewAPI(meta.ExtractScwClient(m)) + if err != nil { return err } + marketplaceImage, err := marketplaceAPI.GetLocalImage(&marketplace.GetLocalImageRequest{ LocalImageID: server.Server.Image.ID, }, scw.WithContext(ctx)) @@ -1250,6 +1276,7 @@ func customDiffInstanceServerImage(ctx context.Context, diff *schema.ResourceDif return err } + if marketplaceImage.Label != image.ID { return diff.ForceNew("image") } @@ -1262,6 +1289,7 @@ func ResourceInstanceServerMigrate(ctx context.Context, d *schema.ResourceData, if err != nil { return fmt.Errorf("failed to wait for server before changing server type: %w", err) } + beginningState := server.State err = reachState(ctx, api, zone, id, instanceSDK.ServerStateStopped) @@ -1324,6 +1352,7 @@ func ResourceInstanceServerUpdateIPs(ctx context.Context, d *schema.ResourceData if isAttached { continue } + _, err := instanceAPI.UpdateIP(&instanceSDK.UpdateIPRequest{ Zone: zone, IP: ipID, @@ -1404,6 +1433,7 @@ func instanceServerVolumesUpdate(ctx context.Context, d *schema.ResourceData, ap for i, volumeID := range raw.([]interface{}) { volumeHasChange := d.HasChange("additional_volume_ids." + strconv.Itoa(i)) + volume, err := api.GetUnknownVolume(&GetUnknownVolumeRequest{ VolumeID: zonal.ExpandID(volumeID).ID, Zone: zone, @@ -1416,6 +1446,7 @@ func instanceServerVolumesUpdate(ctx context.Context, d *schema.ResourceData, ap if volumeHasChange && !serverIsStopped && volume.IsLocal() && volume.IsAttached() { return nil, errors.New("instance must be stopped to change local volumes") } + volumes[strconv.Itoa(i+1)] = volume.VolumeTemplate() } diff --git a/internal/services/instance/server_data_source.go b/internal/services/instance/server_data_source.go index d217b1ee1e..c40517a362 100644 --- a/internal/services/instance/server_data_source.go +++ b/internal/services/instance/server_data_source.go @@ -44,6 +44,7 @@ func DataSourceInstanceServerRead(ctx context.Context, d *schema.ResourceData, m serverID, ok := d.GetOk("server_id") if !ok { serverName := d.Get("name").(string) + res, err := instanceAPI.ListServers(&instance.ListServersRequest{ Zone: zone, Name: types.ExpandStringPtr(serverName), diff --git a/internal/services/instance/server_data_source_test.go b/internal/services/instance/server_data_source_test.go index 9d5da35efa..09ae6c7320 100644 --- a/internal/services/instance/server_data_source_test.go +++ b/internal/services/instance/server_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccDataSourceServer_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + serverName := "tf-server" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/instance/server_test.go b/internal/services/instance/server_test.go index daa5fb11a9..1df6219e5b 100644 --- a/internal/services/instance/server_test.go +++ b/internal/services/instance/server_test.go @@ -1427,12 +1427,14 @@ func serverIDsAreDifferent(nameFirst, nameSecond string) resource.TestCheckFunc if !ok { return fmt.Errorf("resource was not found: %s", nameFirst) } + idFirst := rs.Primary.ID rs, ok = s.RootModule().Resources[nameSecond] if !ok { return fmt.Errorf("resource was not found: %s", nameSecond) } + idSecond := rs.Primary.ID if idFirst == idSecond { diff --git a/internal/services/instance/servers_data_source.go b/internal/services/instance/servers_data_source.go index f06b0d2574..444b3148b4 100644 --- a/internal/services/instance/servers_data_source.go +++ b/internal/services/instance/servers_data_source.go @@ -151,6 +151,7 @@ func DataSourceInstanceServersRead(ctx context.Context, d *schema.ResourceData, if err != nil { return diag.FromErr(err) } + res, err := instanceAPI.ListServers(&instance.ListServersRequest{ Zone: zone, Name: types.ExpandStringPtr(d.Get("name")), @@ -164,51 +165,64 @@ func DataSourceInstanceServersRead(ctx context.Context, d *schema.ResourceData, var diags diag.Diagnostics servers := []interface{}(nil) + for _, server := range res.Servers { rawServer := make(map[string]interface{}) rawServer["id"] = zonal.NewID(server.Zone, server.ID).String() + if server.PublicIP != nil { //nolint:staticcheck rawServer["public_ip"] = server.PublicIP.Address.String() //nolint:staticcheck } + if server.PublicIPs != nil { rawServer["public_ips"] = flattenServerPublicIPs(server.Zone, server.PublicIPs) } + if server.PrivateIP != nil { rawServer["private_ip"] = *server.PrivateIP } + state, err := serverStateFlatten(server.State) if err != nil { diags = append(diags, diag.FromErr(err)...) continue } + rawServer["state"] = state rawServer["zone"] = string(zone) rawServer["name"] = server.Name rawServer["boot_type"] = server.BootType rawServer["type"] = server.CommercialType + if len(server.Tags) > 0 { rawServer["tags"] = server.Tags } + rawServer["security_group_id"] = zonal.NewID(zone, server.SecurityGroup.ID).String() if server.EnableIPv6 != nil { //nolint:staticcheck rawServer["enable_ipv6"] = server.EnableIPv6 //nolint:staticcheck } + rawServer["enable_dynamic_ip"] = server.DynamicIPRequired rawServer["routed_ip_enabled"] = server.RoutedIPEnabled //nolint:staticcheck rawServer["organization_id"] = server.Organization rawServer["project_id"] = server.Project + if server.Image != nil { rawServer["image"] = server.Image.ID } + if server.PlacementGroup != nil { rawServer["placement_group_id"] = zonal.NewID(zone, server.PlacementGroup.ID).String() rawServer["placement_group_policy_respected"] = server.PlacementGroup.PolicyRespected } + if server.IPv6 != nil { //nolint:staticcheck rawServer["ipv6_address"] = server.IPv6.Address.String() //nolint:staticcheck rawServer["ipv6_gateway"] = server.IPv6.Gateway.String() //nolint:staticcheck - prefixLength, err := strconv.Atoi(server.IPv6.Netmask) //nolint:staticcheck + + prefixLength, err := strconv.Atoi(server.IPv6.Netmask) //nolint:staticcheck if err != nil { diags = append(diags, diag.FromErr(fmt.Errorf("failed to read ipv6 netmask: %w", err))...) @@ -220,6 +234,7 @@ func DataSourceInstanceServersRead(ctx context.Context, d *schema.ResourceData, servers = append(servers, rawServer) } + if len(diags) > 0 { return diags } diff --git a/internal/services/instance/snapshot_data_source.go b/internal/services/instance/snapshot_data_source.go index 5b93b99d94..318aa0dbab 100644 --- a/internal/services/instance/snapshot_data_source.go +++ b/internal/services/instance/snapshot_data_source.go @@ -43,6 +43,7 @@ func DataSourceInstanceSnapshotRead(ctx context.Context, d *schema.ResourceData, snapshotID, ok := d.GetOk("snapshot_id") if !ok { snapshotName := d.Get("name").(string) + res, err := instanceAPI.ListSnapshots(&instance.ListSnapshotsRequest{ Zone: zone, Name: types.ExpandStringPtr(snapshotName), @@ -72,6 +73,7 @@ func DataSourceInstanceSnapshotRead(ctx context.Context, d *schema.ResourceData, if err != nil { return diag.FromErr(err) } + diags := ResourceInstanceSnapshotRead(ctx, d, m) if len(diags) > 0 { return diags diff --git a/internal/services/instance/snapshot_data_source_test.go b/internal/services/instance/snapshot_data_source_test.go index ccf058bc35..9d58b106a0 100644 --- a/internal/services/instance/snapshot_data_source_test.go +++ b/internal/services/instance/snapshot_data_source_test.go @@ -11,6 +11,7 @@ import ( func TestAccDataSourceSnapshot_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + snapshotName := "tf-snapshot-ds-basic" resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/instance/testfuncs/checks.go b/internal/services/instance/testfuncs/checks.go index dee00bb493..d129219277 100644 --- a/internal/services/instance/testfuncs/checks.go +++ b/internal/services/instance/testfuncs/checks.go @@ -87,6 +87,7 @@ func IsServerRootVolumeDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { if err != nil { return err } + rootVolumeID := locality.ExpandID(localizedRootVolumeID) api := instance.NewBlockAndInstanceAPI(meta.ExtractScwClient(tt.Meta)) @@ -150,11 +151,14 @@ func DoesImageExists(tt *acctest.TestTools, n string) resource.TestCheckFunc { if !ok { return fmt.Errorf("not found: %s", n) } + zone, ID, err := zonal.ParseID(rs.Primary.ID) if err != nil { return err } + instanceAPI := instanceSDK.NewAPI(tt.Meta.ScwClient()) + _, err = instanceAPI.GetImage(&instanceSDK.GetImageRequest{ ImageID: ID, Zone: zone, diff --git a/internal/services/instance/testfuncs/sweep.go b/internal/services/instance/testfuncs/sweep.go index 4d300aac1a..5ede2f864c 100644 --- a/internal/services/instance/testfuncs/sweep.go +++ b/internal/services/instance/testfuncs/sweep.go @@ -45,6 +45,7 @@ func AddTestSweepers() { func testSweepVolume(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { instanceAPI := instanceSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the volumes in (%s)", zone) listVolumesResponse, err := instanceAPI.ListVolumes(&instanceSDK.ListVolumesRequest{ @@ -73,6 +74,7 @@ func testSweepVolume(_ string) error { func testSweepSnapshot(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { api := instanceSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying instance snapshots in (%+v)", zone) listSnapshotsResponse, err := api.ListSnapshots(&instanceSDK.ListSnapshotsRequest{ @@ -99,7 +101,9 @@ func testSweepSnapshot(_ string) error { func testSweepServer(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { instanceAPI := instanceSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the instanceSDK server in (%s)", zone) + listServers, err := instanceAPI.ListServers(&instanceSDK.ListServersRequest{Zone: zone}, scw.WithAllPages()) if err != nil { logging.L.Warningf("error listing servers in (%s) in sweeper: %s", zone, err) @@ -135,6 +139,7 @@ func testSweepServer(_ string) error { func testSweepSecurityGroup(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { instanceAPI := instanceSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the security groups in (%s)", zone) listResp, err := instanceAPI.ListSecurityGroups(&instanceSDK.ListSecurityGroupsRequest{ @@ -151,6 +156,7 @@ func testSweepSecurityGroup(_ string) error { if securityGroup.ProjectDefault { continue } + err = instanceAPI.DeleteSecurityGroup(&instanceSDK.DeleteSecurityGroupRequest{ Zone: zone, SecurityGroupID: securityGroup.ID, @@ -167,7 +173,9 @@ func testSweepSecurityGroup(_ string) error { func testSweepPlacementGroup(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { instanceAPI := instanceSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the instance placement group in (%s)", zone) + listPlacementGroups, err := instanceAPI.ListPlacementGroups(&instanceSDK.ListPlacementGroupsRequest{ Zone: zone, }, scw.WithAllPages()) @@ -219,6 +227,7 @@ func testSweepIP(_ string) error { func testSweepImage(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { api := instanceSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying instance images in (%+v)", zone) listImagesResponse, err := api.ListImages(&instanceSDK.ListImagesRequest{ diff --git a/internal/services/instance/types.go b/internal/services/instance/types.go index f285af5cdd..b4e8d1d554 100644 --- a/internal/services/instance/types.go +++ b/internal/services/instance/types.go @@ -10,10 +10,12 @@ import ( func (ph *privateNICsHandler) flatPrivateNICs() error { privateNICsMap := make(map[string]*instance.PrivateNIC) + res, err := ph.instanceAPI.ListPrivateNICs(&instance.ListPrivateNICsRequest{Zone: ph.zone, ServerID: ph.serverID}) if err != nil { return err } + for _, p := range res.PrivateNics { privateNICsMap[p.PrivateNetworkID] = p } @@ -28,6 +30,7 @@ func expandImageExtraVolumesTemplates(snapshotIDs []string) map[string]*instance if len(snapshotIDs) == 0 { return volTemplates } + for i, snapID := range snapshotIDs { volTemplate := &instance.VolumeTemplate{ ID: snapID, @@ -43,6 +46,7 @@ func expandImageExtraVolumesUpdateTemplates(snapshotIDs []string) map[string]*in if len(snapshotIDs) == 0 { return volTemplates } + for i, snapID := range snapshotIDs { volTemplate := &instance.VolumeImageUpdateTemplate{ ID: snapID, @@ -55,12 +59,14 @@ func expandImageExtraVolumesUpdateTemplates(snapshotIDs []string) map[string]*in func flattenImageExtraVolumes(volumes map[string]*instance.Volume, zone scw.Zone) interface{} { volumesFlat := []map[string]interface{}(nil) + for _, volume := range volumes { server := map[string]interface{}{} if volume.Server != nil { server["id"] = volume.Server.ID server["name"] = volume.Server.Name } + volumeFlat := map[string]interface{}{ "id": zonal.NewIDString(zone, volume.ID), "name": volume.Name, diff --git a/internal/services/instance/user_data.go b/internal/services/instance/user_data.go index e8f066f186..4078e3d4d8 100644 --- a/internal/services/instance/user_data.go +++ b/internal/services/instance/user_data.go @@ -63,6 +63,7 @@ func ResourceInstanceUserDataCreate(ctx context.Context, d *schema.ResourceData, } serverID := locality.ExpandID(d.Get("server_id").(string)) + server, err := waitForServer(ctx, instanceAPI, zone, serverID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -129,6 +130,7 @@ func ResourceInstanceUserDataRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + _ = d.Set("server_id", zonal.NewID(zone, server.ID).String()) _ = d.Set("key", key) _ = d.Set("value", string(userDataValue)) diff --git a/internal/services/instance/volume.go b/internal/services/instance/volume.go index 58a453a2bd..5793be25e7 100644 --- a/internal/services/instance/volume.go +++ b/internal/services/instance/volume.go @@ -96,6 +96,7 @@ func ResourceInstanceVolumeCreate(ctx context.Context, d *schema.ResourceData, m Project: types.ExpandStringPtr(d.Get("project_id")), } tags := types.ExpandStrings(d.Get("tags")) + if len(tags) > 0 { createVolumeRequest.Tags = tags } @@ -196,6 +197,7 @@ func ResourceInstanceVolumeUpdate(ctx context.Context, d *schema.ResourceData, m if d.Get("type") != instanceSDK.VolumeVolumeTypeBSSD.String() { return diag.FromErr(errors.New("only block volume can be resized")) } + if oldSize, newSize := d.GetChange("size_in_gb"); oldSize.(int) > newSize.(int) { return diag.FromErr(errors.New("block volumes cannot be resized down")) } @@ -206,6 +208,7 @@ func ResourceInstanceVolumeUpdate(ctx context.Context, d *schema.ResourceData, m } volumeSizeInBytes := scw.Size(uint64(d.Get("size_in_gb").(int)) * gb) + _, err = instanceAPI.UpdateVolume(&instanceSDK.UpdateVolumeRequest{ VolumeID: id, Zone: zone, @@ -214,6 +217,7 @@ func ResourceInstanceVolumeUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(fmt.Errorf("couldn't resize volume: %s", err)) } + _, err = waitForVolume(ctx, instanceAPI, zone, id, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/instance/volume_data_source.go b/internal/services/instance/volume_data_source.go index 85972274a2..bb8834fb43 100644 --- a/internal/services/instance/volume_data_source.go +++ b/internal/services/instance/volume_data_source.go @@ -43,6 +43,7 @@ func DataSourceInstanceVolumeRead(ctx context.Context, d *schema.ResourceData, m volumeID, ok := d.GetOk("volume_id") if !ok { // Get volumes by zone and name. volumeName := d.Get("name").(string) + res, err := instanceAPI.ListVolumes(&instance.ListVolumesRequest{ Zone: zone, Name: types.ExpandStringPtr(volumeName), @@ -66,6 +67,7 @@ func DataSourceInstanceVolumeRead(ctx context.Context, d *schema.ResourceData, m zonedID := datasource.NewZonedID(volumeID, zone) d.SetId(zonedID) + err = d.Set("volume_id", zonedID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/instance/volume_data_source_test.go b/internal/services/instance/volume_data_source_test.go index 9d5c13d5bc..23da1f4221 100644 --- a/internal/services/instance/volume_data_source_test.go +++ b/internal/services/instance/volume_data_source_test.go @@ -11,6 +11,7 @@ import ( func TestAccDataSourceVolume_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + volumeName := "tf-volume" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/instance/volume_test.go b/internal/services/instance/volume_test.go index a7651fa586..8b11f972b9 100644 --- a/internal/services/instance/volume_test.go +++ b/internal/services/instance/volume_test.go @@ -207,6 +207,7 @@ func isVolumePresent(tt *acctest.TestTools, n string) resource.TestCheckFunc { } instanceAPI := instanceSDK.NewAPI(tt.Meta.ScwClient()) + _, err = instanceAPI.GetVolume(&instanceSDK.GetVolumeRequest{ VolumeID: id, Zone: zone, @@ -222,6 +223,7 @@ func isVolumePresent(tt *acctest.TestTools, n string) resource.TestCheckFunc { func isVolumeDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { return func(state *terraform.State) error { instanceAPI := instanceSDK.NewAPI(tt.Meta.ScwClient()) + for _, rs := range state.RootModule().Resources { if rs.Type != "scaleway_instance_volume" { continue diff --git a/internal/services/iot/data_source_iot_device.go b/internal/services/iot/data_source_iot_device.go index 1ce39063cc..d8dd88f1be 100644 --- a/internal/services/iot/data_source_iot_device.go +++ b/internal/services/iot/data_source_iot_device.go @@ -48,7 +48,9 @@ func DataSourceIotDeviceRead(ctx context.Context, d *schema.ResourceData, m inte return diag.FromErr(err) } } + deviceName := d.Get("name").(string) + res, err := api.ListDevices(&iot.ListDevicesRequest{ Region: region, Name: types.ExpandStringPtr(deviceName), @@ -72,14 +74,17 @@ func DataSourceIotDeviceRead(ctx context.Context, d *schema.ResourceData, m inte regionalID := datasource.NewRegionalID(deviceID, region) d.SetId(regionalID) + err = d.Set("device_id", regionalID) if err != nil { return diag.FromErr(err) } + diags := ResourceIotDeviceRead(ctx, d, m) if diags != nil { return diags } + if d.Id() == "" { return diag.Errorf("IOT Device not found (%s)", regionalID) } diff --git a/internal/services/iot/data_source_iot_hub.go b/internal/services/iot/data_source_iot_hub.go index d23cd23623..6bac6dbc08 100644 --- a/internal/services/iot/data_source_iot_hub.go +++ b/internal/services/iot/data_source_iot_hub.go @@ -41,6 +41,7 @@ func DataSourceIotHubRead(ctx context.Context, d *schema.ResourceData, m interfa hubID, ok := d.GetOk("hub_id") if !ok { hubName := d.Get("name").(string) + res, err := api.ListHubs(&iot.ListHubsRequest{ Region: region, ProjectID: types.ExpandStringPtr(d.Get("project_id")), @@ -64,14 +65,17 @@ func DataSourceIotHubRead(ctx context.Context, d *schema.ResourceData, m interfa regionalID := datasource.NewRegionalID(hubID, region) d.SetId(regionalID) + err = d.Set("hub_id", regionalID) if err != nil { return diag.FromErr(err) } + diags := ResourceIotHubRead(ctx, d, m) if diags != nil { return diags } + if d.Id() == "" { return diag.Errorf("IOT Hub not found (%s)", regionalID) } diff --git a/internal/services/iot/device.go b/internal/services/iot/device.go index 44c307a43f..8a241d4704 100644 --- a/internal/services/iot/device.go +++ b/internal/services/iot/device.go @@ -216,6 +216,7 @@ func ResourceIotDeviceCreate(ctx context.Context, d *schema.ResourceData, m inte if policy, ok := d.GetOk(fqfnS + iotPolicySuffix); ok { mfSet.Policy = iot.DeviceMessageFiltersRulePolicy(policy.(string)) } + if topics, ok := d.GetOk(fqfnS + iotTopicsSuffix); ok { mfSet.Topics = scw.StringsPtr(types.ExpandStringsOrEmpty(topics)) } @@ -230,6 +231,7 @@ func ResourceIotDeviceCreate(ctx context.Context, d *schema.ResourceData, m inte if policy, ok := d.GetOk(fqfnP + iotPolicySuffix); ok { mfSet.Policy = iot.DeviceMessageFiltersRulePolicy(policy.(string)) } + if topics, ok := d.GetOk(fqfnP + iotTopicsSuffix); ok { mfSet.Topics = scw.StringsPtr(types.ExpandStringsOrEmpty(topics)) } @@ -316,6 +318,7 @@ func ResourceIotDeviceRead(ctx context.Context, d *schema.ResourceData, m interf (device.MessageFilters.Publish.Topics != nil && len(*device.MessageFilters.Publish.Topics) != 0) { mfHasNonDefaultChange = true } + if device.MessageFilters.Subscribe.Policy != iot.DeviceMessageFiltersRulePolicyReject || (device.MessageFilters.Subscribe.Topics != nil && len(*device.MessageFilters.Subscribe.Topics) != 0) { mfHasNonDefaultChange = true diff --git a/internal/services/iot/device_test.go b/internal/services/iot/device_test.go index 96c67d9619..3ec52edb25 100644 --- a/internal/services/iot/device_test.go +++ b/internal/services/iot/device_test.go @@ -70,6 +70,7 @@ func TestAccDevice_Minimal(t *testing.T) { func TestAccDevice_MessageFilters(t *testing.T) { t.Skip("Some checks seem to be flaky.") + tt := acctest.NewTestTools(t) defer tt.Cleanup() resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/iot/helpers_iot.go b/internal/services/iot/helpers_iot.go index 09e5082010..75c3a35bcd 100644 --- a/internal/services/iot/helpers_iot.go +++ b/internal/services/iot/helpers_iot.go @@ -77,12 +77,16 @@ func computeIotHubMQTTCa(ctx context.Context, mqttCaURL string, m interface{}) ( if mqttCaURL == "" { return "", nil } + var mqttCa *http.Response + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, mqttCaURL, nil) + mqttCa, err := meta.ExtractHTTPClient(m).Do(req) if err != nil { return "", err } + defer mqttCa.Body.Close() resp, _ := io.ReadAll(mqttCa.Body) diff --git a/internal/services/iot/hub.go b/internal/services/iot/hub.go index d4865d573e..4711dfb32f 100644 --- a/internal/services/iot/hub.go +++ b/internal/services/iot/hub.go @@ -130,6 +130,7 @@ func ResourceIotHubCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + req := &iot.CreateHubRequest{ Region: region, Name: types.ExpandOrGenerateString(d.Get("name"), "hub"), @@ -152,6 +153,7 @@ func ResourceIotHubCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + d.SetId(regional.NewIDString(region, res.ID)) _, err = waitIotHub(ctx, iotAPI, region, res.ID, d.Timeout(schema.TimeoutCreate)) @@ -200,6 +202,7 @@ func ResourceIotHubCreate(ctx context.Context, d *schema.ResourceData, m interfa return diag.FromErr(err) } } + MQTTUrl := computeIotHubCaURL(req.ProductPlan, region) _ = d.Set("mqtt_ca_url", MQTTUrl) @@ -243,6 +246,7 @@ func ResourceIotHubRead(ctx context.Context, d *schema.ResourceData, m interface _ = d.Set("device_auto_provisioning", response.EnableDeviceAutoProvisioning) _ = d.Set("mqtt_ca_url", computeIotHubCaURL(response.ProductPlan, region)) mqttURL := d.Get("mqtt_ca_url") + mqttCa, err := computeIotHubMQTTCa(ctx, fmt.Sprintf("%v", mqttURL), m) if err != nil { _ = diag.Diagnostic{ @@ -252,6 +256,7 @@ func ResourceIotHubRead(ctx context.Context, d *schema.ResourceData, m interface Detail: err.Error(), } } + _ = d.Set("mqtt_ca", mqttCa) return nil @@ -281,6 +286,7 @@ func ResourceIotHubUpdate(ctx context.Context, d *schema.ResourceData, m interfa HubID: hubID, }, scw.WithContext(ctx)) } + if err != nil { return diag.FromErr(err) } diff --git a/internal/services/iot/routes.go b/internal/services/iot/routes.go index cfef42ffac..6c761bd83e 100644 --- a/internal/services/iot/routes.go +++ b/internal/services/iot/routes.go @@ -195,6 +195,7 @@ func ResourceIotRouteCreate(ctx context.Context, d *schema.ResourceData, m inter } hubID := zonal.ExpandID(d.Get("hub_id")).ID + _, err = waitIotHub(ctx, iotAPI, region, hubID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -316,6 +317,7 @@ func ResourceIotRouteDelete(ctx context.Context, d *schema.ResourceData, m inter } hubID := zonal.ExpandID(d.Get("hub_id")).ID + _, err = waitIotHub(ctx, iotAPI, region, hubID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/iot/routes_test.go b/internal/services/iot/routes_test.go index 3f314cbef0..c086219105 100644 --- a/internal/services/iot/routes_test.go +++ b/internal/services/iot/routes_test.go @@ -84,8 +84,10 @@ func TestAccRoute_S3(t *testing.T) { if !*acctest.UpdateCassettes { t.Skip("Skipping ObjectStorage test as this kind of resource can't be deleted before 24h") } + tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("tf-tests-scaleway-iot-route-s3") resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/iot/testfuncs/sweep.go b/internal/services/iot/testfuncs/sweep.go index c820e398ca..d633ef5830 100644 --- a/internal/services/iot/testfuncs/sweep.go +++ b/internal/services/iot/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepHub(_ string) error { return acctest.SweepRegions(scw.AllRegions, func(scwClient *scw.Client, region scw.Region) error { iotAPI := iotSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the iot hub in (%s)", region) + listHubs, err := iotAPI.ListHubs(&iotSDK.ListHubsRequest{Region: region}, scw.WithAllPages()) if err != nil { logging.L.Debugf("sweeper: destroying the iot hub in (%s)", region) diff --git a/internal/services/ipam/ip.go b/internal/services/ipam/ip.go index 1be7753afc..c17b137bdf 100644 --- a/internal/services/ipam/ip.go +++ b/internal/services/ipam/ip.go @@ -185,6 +185,7 @@ func ResourceIPAMIPCreate(ctx context.Context, d *schema.ResourceData, m interfa address, addressOk := d.GetOk("address") if addressOk { addressStr := address.(string) + parsedIP, _, err := net.ParseCIDR(addressStr) if err != nil { parsedIP = net.ParseIP(addressStr) @@ -192,6 +193,7 @@ func ResourceIPAMIPCreate(ctx context.Context, d *schema.ResourceData, m interfa return diag.FromErr(fmt.Errorf("error parsing IP address: %s", err)) } } + req.Address = scw.IPPtr(parsedIP) } @@ -218,6 +220,7 @@ func ResourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interface if err != nil { return diag.FromErr(err) } + vpcAPI, err := vpc.NewAPI(m) if err != nil { return diag.FromErr(err) @@ -238,6 +241,7 @@ func ResourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interface } privateNetworkID := "" + if source, ok := d.GetOk("source"); ok { sourceData := expandIPSource(source) if sourceData.PrivateNetworkID != nil { @@ -250,6 +254,7 @@ func ResourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interface } ipv4Subnets, ipv6Subnets := vpc.FlattenAndSortSubnets(pn.Subnets) + var found bool if d.Get("is_ipv6").(bool) { @@ -268,6 +273,7 @@ func ResourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interface if err != nil { return diag.FromErr(err) } + _ = d.Set("address", address) _ = d.Set("source", flattenIPSource(res.Source, privateNetworkID)) _ = d.Set("resource", flattenIPResource(res.Resource)) @@ -276,12 +282,15 @@ func ResourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interface _ = d.Set("updated_at", types.FlattenTime(res.UpdatedAt)) _ = d.Set("is_ipv6", res.IsIPv6) _ = d.Set("region", region) + if res.Zone != nil { _ = d.Set("zone", res.Zone.String()) } + if len(res.Tags) > 0 { _ = d.Set("tags", res.Tags) } + _ = d.Set("reverses", flattenIPReverses(res.Reverses)) return nil diff --git a/internal/services/ipam/ip_data_source.go b/internal/services/ipam/ip_data_source.go index af1aa42ef5..73fc734576 100644 --- a/internal/services/ipam/ip_data_source.go +++ b/internal/services/ipam/ip_data_source.go @@ -129,6 +129,7 @@ func DataSourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interfa } var address, addressCidr string + var ip scw.IPNet IPID, ok := d.GetOk("ipam_ip_id") @@ -191,10 +192,12 @@ func DataSourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return retry.NonRetryableError(err) } + if len(resp.IPs) == 0 { // Retry if no IPs are found return retry.RetryableError(errors.New("no ip found with given filters")) } + if len(resp.IPs) > 1 { return retry.NonRetryableError(errors.New("more than one ip found with given filter")) } @@ -220,6 +223,7 @@ func DataSourceIPAMIPRead(ctx context.Context, d *schema.ResourceData, m interfa } address = ip.IP.String() + addressCidr, err = types.FlattenIPNet(ip) if err != nil { return diag.FromErr(err) diff --git a/internal/services/ipam/ip_reverse_dns.go b/internal/services/ipam/ip_reverse_dns.go index 5ef95772b4..27412aa17e 100644 --- a/internal/services/ipam/ip_reverse_dns.go +++ b/internal/services/ipam/ip_reverse_dns.go @@ -67,6 +67,7 @@ func ResourceIPAMIPReverseDNSCreate(ctx context.Context, d *schema.ResourceData, } d.SetId(regional.NewIDString(region, res.ID)) + if hostname, ok := d.GetOk("hostname"); ok { reverse := &ipam.Reverse{ Hostname: hostname.(string), @@ -110,6 +111,7 @@ func ResourceIPAMIPReverseDNSRead(ctx context.Context, d *schema.ResourceData, m managedHostname := d.Get("hostname").(string) managedAddress := d.Get("address").(string) + for _, reverse := range res.Reverses { if reverse.Hostname == managedHostname && reverse.Address.String() == managedAddress { _ = d.Set("hostname", reverse.Hostname) diff --git a/internal/services/ipam/ip_reverse_dns_test.go b/internal/services/ipam/ip_reverse_dns_test.go index ca238633d4..8c890796a0 100644 --- a/internal/services/ipam/ip_reverse_dns_test.go +++ b/internal/services/ipam/ip_reverse_dns_test.go @@ -14,6 +14,7 @@ import ( func TestAccIPAMIPReverseDNS_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testDNSZone := "tf-reverse-ipam." + acctest.TestDomain resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/ipam/ips_data_source.go b/internal/services/ipam/ips_data_source.go index 841258955e..e62a5d5eaa 100644 --- a/internal/services/ipam/ips_data_source.go +++ b/internal/services/ipam/ips_data_source.go @@ -185,6 +185,7 @@ func DataSourceIPAMIPsRead(ctx context.Context, d *schema.ResourceData, m interf } ips := []interface{}(nil) + for _, ip := range resp.IPs { address, err := types.FlattenIPNet(ip.Address) if err != nil { diff --git a/internal/services/ipam/types.go b/internal/services/ipam/types.go index 1731e434d6..603a0384c7 100644 --- a/internal/services/ipam/types.go +++ b/internal/services/ipam/types.go @@ -18,6 +18,7 @@ import ( // invalid -> invalid func expandLastID(i interface{}) string { composedID := i.(string) + elems := strings.Split(composedID, "/") for i := len(elems) - 1; i >= 0; i-- { if validation.IsUUID(elems[i]) { diff --git a/internal/services/jobs/helpers.go b/internal/services/jobs/helpers.go index 799124ab03..0b0c54131b 100644 --- a/internal/services/jobs/helpers.go +++ b/internal/services/jobs/helpers.go @@ -67,6 +67,7 @@ func expandJobDefinitionCron(i any) *JobDefinitionCron { if len(rawList) == 0 { return nil } + rawCron := rawList[0].(map[string]any) return &JobDefinitionCron{ diff --git a/internal/services/jobs/testfuncs/sweep.go b/internal/services/jobs/testfuncs/sweep.go index 78c3d5b678..798a1324e3 100644 --- a/internal/services/jobs/testfuncs/sweep.go +++ b/internal/services/jobs/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepJobDefinition(_ string) error { return acctest.SweepRegions((&jobsSDK.API{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { jobsAPI := jobsSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the jobs definitions in (%s)", region) + listJobDefinitions, err := jobsAPI.ListJobDefinitions( &jobsSDK.ListJobDefinitionsRequest{ Region: region, diff --git a/internal/services/k8s/cluster.go b/internal/services/k8s/cluster.go index 446692d390..9399cfc29d 100644 --- a/internal/services/k8s/cluster.go +++ b/internal/services/k8s/cluster.go @@ -472,6 +472,7 @@ func ResourceK8SClusterCreate(ctx context.Context, d *schema.ResourceData, m int if okAutoUpgradeEnable && autoUpgradeEnable.(bool) && !versionIsOnlyMinor { return append(diag.FromErr(errors.New("only minor version x.y can be used with auto upgrade enabled")), diags...) } + if versionIsOnlyMinor && !autoUpgradeEnable.(bool) { return append(diag.FromErr(errors.New("minor version x.y must only be used with auto upgrade enabled")), diags...) } @@ -499,6 +500,7 @@ func ResourceK8SClusterCreate(ctx context.Context, d *schema.ResourceData, m int } d.SetId(regional.NewIDString(region, res.ID)) + if strings.Contains(clusterType.(string), "multicloud") { // In case of multi-cloud, we do not have the guarantee that a pool will be created in Scaleway. _, err = waitCluster(ctx, k8sAPI, region, res.ID, d.Timeout(schema.TimeoutCreate)) @@ -506,6 +508,7 @@ func ResourceK8SClusterCreate(ctx context.Context, d *schema.ResourceData, m int // If we are not in multi-cloud, we can wait for the pool to be created. _, err = waitClusterPool(ctx, k8sAPI, region, res.ID, d.Timeout(schema.TimeoutCreate)) } + if err != nil { return append(diag.FromErr(err), diags...) } @@ -559,6 +562,7 @@ func ResourceK8SClusterRead(ctx context.Context, d *schema.ResourceData, m inter return diag.FromErr(err) } } + _ = d.Set("version", version) // autoscaler_config @@ -572,6 +576,7 @@ func ResourceK8SClusterRead(ctx context.Context, d *schema.ResourceData, m inter pnID := types.FlattenStringPtr(cluster.PrivateNetworkID) clusterType := d.Get("type").(string) _ = d.Set("private_network_id", pnID) + if pnID == "" && !strings.HasPrefix(clusterType, "multicloud") { diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, @@ -596,6 +601,7 @@ func ResourceK8SClusterRead(ctx context.Context, d *schema.ResourceData, m inter } else { diags = append(diags, diag.FromErr(err)...) } + _ = d.Set("kubeconfig", []map[string]interface{}{kubeconfig}) return diags @@ -613,6 +619,7 @@ func ResourceK8SClusterUpdate(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + _, err = k8sAPI.SetClusterType(&k8s.SetClusterTypeRequest{ Region: region, ClusterID: clusterID, @@ -698,6 +705,7 @@ func ResourceK8SClusterUpdate(ctx context.Context, d *schema.ResourceData, m int if autoupgradeEnabled && !versionIsOnlyMinor { return append(diag.FromErr(errors.New("only minor version x.y can be used with auto upgrade enabled")), diags...) } + if versionIsOnlyMinor && !autoupgradeEnabled { return append(diag.FromErr(errors.New("minor version x.y must only be used with auto upgrade enabled")), diags...) } @@ -712,7 +720,6 @@ func ResourceK8SClusterUpdate(ctx context.Context, d *schema.ResourceData, m int if d.HasChange("version") { // maybe it's a change from minor to patch or patch to minor // we need to check the current version - clusterResp, err := k8sAPI.GetCluster(&k8s.GetClusterRequest{ ClusterID: clusterID, Region: region, @@ -846,6 +853,7 @@ func ResourceK8SClusterUpdate(ctx context.Context, d *schema.ResourceData, m int Version: version, UpgradePools: true, } + _, err = k8sAPI.UpgradeCluster(upgradeRequest) if err != nil { return append(diag.FromErr(err), diags...) diff --git a/internal/services/k8s/cluster_data_source.go b/internal/services/k8s/cluster_data_source.go index 4a816ae24e..70c5f571f2 100644 --- a/internal/services/k8s/cluster_data_source.go +++ b/internal/services/k8s/cluster_data_source.go @@ -45,6 +45,7 @@ func DataSourceK8SClusterRead(ctx context.Context, d *schema.ResourceData, m int clusterID, ok := d.GetOk("cluster_id") if !ok { clusterName := d.Get("name").(string) + res, err := k8sAPI.ListClusters(&k8s.ListClustersRequest{ Region: region, Name: types.ExpandStringPtr(clusterName), diff --git a/internal/services/k8s/cluster_data_source_test.go b/internal/services/k8s/cluster_data_source_test.go index c8425e373e..a408b1774d 100644 --- a/internal/services/k8s/cluster_data_source_test.go +++ b/internal/services/k8s/cluster_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccDataSourceCluster_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + clusterName := "tf-cluster" version := testAccK8SClusterGetLatestK8SVersion(tt) resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/k8s/cluster_test.go b/internal/services/k8s/cluster_test.go index 9c9006fd99..fb3a359d47 100644 --- a/internal/services/k8s/cluster_test.go +++ b/internal/services/k8s/cluster_test.go @@ -17,10 +17,12 @@ import ( func testAccK8SClusterGetLatestK8SVersion(tt *acctest.TestTools) string { api := k8sSDK.NewAPI(tt.Meta.ScwClient()) + versions, err := api.ListVersions(&k8sSDK.ListVersionsRequest{}) if err != nil { tt.T.Fatalf("Could not get latestK8SVersion: %s", err) } + if len(versions.Versions) > 1 { latestK8SVersion := versions.Versions[0].Name @@ -32,10 +34,12 @@ func testAccK8SClusterGetLatestK8SVersion(tt *acctest.TestTools) string { func testAccK8SClusterGetLatestK8SVersionMinor(tt *acctest.TestTools) string { api := k8sSDK.NewAPI(tt.Meta.ScwClient()) + versions, err := api.ListVersions(&k8sSDK.ListVersionsRequest{}) if err != nil { tt.T.Fatalf("Could not get latestK8SVersion: %s", err) } + if len(versions.Versions) > 1 { latestK8SVersion := versions.Versions[0].Name latestK8SVersionMinor, _ := k8s.GetMinorVersionFromFull(latestK8SVersion) @@ -48,10 +52,12 @@ func testAccK8SClusterGetLatestK8SVersionMinor(tt *acctest.TestTools) string { func testAccK8SClusterGetPreviousK8SVersion(tt *acctest.TestTools) string { api := k8sSDK.NewAPI(tt.Meta.ScwClient()) + versions, err := api.ListVersions(&k8sSDK.ListVersionsRequest{}) if err != nil { tt.T.Fatalf("Could not get latestK8SVersion: %s", err) } + if len(versions.Versions) > 1 { previousK8SVersion := versions.Versions[1].Name @@ -63,10 +69,12 @@ func testAccK8SClusterGetPreviousK8SVersion(tt *acctest.TestTools) string { func testAccK8SClusterGetPreviousK8SVersionMinor(tt *acctest.TestTools) string { api := k8sSDK.NewAPI(tt.Meta.ScwClient()) + versions, err := api.ListVersions(&k8sSDK.ListVersionsRequest{}) if err != nil { tt.T.Fatalf("Could not get latestK8SVersion: %s", err) } + if len(versions.Versions) > 1 { previousK8SVersion := versions.Versions[1].Name previousK8SVersionMinor, _ := k8s.GetMinorVersionFromFull(previousK8SVersion) @@ -626,6 +634,7 @@ func testAccCheckK8sClusterPrivateNetworkID(tt *acctest.TestTools, clusterName, if clusterPNID == nil { return fmt.Errorf("expected %s private_network_id to be %s, got nil", clusterName, pnID) } + if *clusterPNID != pnID { return fmt.Errorf("expected %s private_network_id to be %s, got %s", clusterName, pnID, *clusterPNID) } @@ -813,6 +822,7 @@ resource "scaleway_k8s_pool" "multicloud" { func testAccCheckK8SClusterTypeChange(clusterType, cni, version string) string { config := "" isKapsule := strings.HasPrefix(clusterType, "kapsule") + if isKapsule { config = ` resource "scaleway_vpc_private_network" "type-change" { diff --git a/internal/services/k8s/helpers_k8s.go b/internal/services/k8s/helpers_k8s.go index 0ad008832b..f2c515c27b 100644 --- a/internal/services/k8s/helpers_k8s.go +++ b/internal/services/k8s/helpers_k8s.go @@ -71,6 +71,7 @@ func k8sGetLatestVersionFromMinor(ctx context.Context, k8sAPI *k8s.API, region s if len(vSplit) != 3 { return "", fmt.Errorf("upstream version %s is not correctly formatted", v.Name) // should never happen } + if versionSplit[0] == vSplit[0] && versionSplit[1] == vSplit[1] { return v.Name, nil } @@ -82,16 +83,20 @@ func k8sGetLatestVersionFromMinor(ctx context.Context, k8sAPI *k8s.API, region s // convert a list of nodes to a list of map func convertNodes(res *k8s.ListNodesResponse) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(res.Nodes)) + for _, node := range res.Nodes { n := make(map[string]interface{}) n["name"] = node.Name n["status"] = node.Status.String() + if node.PublicIPV4 != nil && node.PublicIPV4.String() != types.NetIPNil { //nolint:staticcheck n["public_ip"] = node.PublicIPV4.String() //nolint:staticcheck } + if node.PublicIPV6 != nil && node.PublicIPV6.String() != types.NetIPNil { //nolint:staticcheck n["public_ip_v6"] = node.PublicIPV6.String() //nolint:staticcheck } + result = append(result, n) } diff --git a/internal/services/k8s/k8s_version_data_source.go b/internal/services/k8s/k8s_version_data_source.go index 0fe55ebf0e..802f33b022 100644 --- a/internal/services/k8s/k8s_version_data_source.go +++ b/internal/services/k8s/k8s_version_data_source.go @@ -70,6 +70,7 @@ func DataSourceK8SVersionRead(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + if len(res.Versions) == 0 { return diag.FromErr(errors.New("could not find the latest version")) } @@ -83,6 +84,7 @@ func DataSourceK8SVersionRead(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + version = res } diff --git a/internal/services/k8s/k8s_version_data_source_test.go b/internal/services/k8s/k8s_version_data_source_test.go index 89aa695c22..7f897cf671 100644 --- a/internal/services/k8s/k8s_version_data_source_test.go +++ b/internal/services/k8s/k8s_version_data_source_test.go @@ -87,6 +87,7 @@ func testAccCheckK8SVersionExists(tt *acctest.TestTools, n string) resource.Test } k8sAPI := k8s.NewAPI(tt.Meta.ScwClient()) + _, err = k8sAPI.GetVersion(&k8s.GetVersionRequest{ Region: region, VersionName: name, diff --git a/internal/services/k8s/pool.go b/internal/services/k8s/pool.go index 0d32635db1..cf18a56e33 100644 --- a/internal/services/k8s/pool.go +++ b/internal/services/k8s/pool.go @@ -371,16 +371,20 @@ func ResourceK8SPoolRead(ctx context.Context, d *schema.ResourceData, m interfac _ = d.Set("autoscaling", pool.Autoscaling) _ = d.Set("autohealing", pool.Autohealing) _ = d.Set("current_size", int(pool.Size)) + if !pool.Autoscaling { _ = d.Set("size", int(pool.Size)) } + _ = d.Set("version", pool.Version) _ = d.Set("min_size", int(pool.MinSize)) _ = d.Set("max_size", int(pool.MaxSize)) _ = d.Set("root_volume_type", pool.RootVolumeType) + if pool.RootVolumeSize != nil { _ = d.Set("root_volume_size_in_gb", int(*pool.RootVolumeSize)/1e9) } + _ = d.Set("tags", pool.Tags) _ = d.Set("container_runtime", pool.ContainerRuntime) _ = d.Set("created_at", pool.CreatedAt.Format(time.RFC3339)) diff --git a/internal/services/k8s/pool_data_source.go b/internal/services/k8s/pool_data_source.go index 1e97173ba8..bcf05bc6e5 100644 --- a/internal/services/k8s/pool_data_source.go +++ b/internal/services/k8s/pool_data_source.go @@ -48,6 +48,7 @@ func DataSourceK8SPoolRead(ctx context.Context, d *schema.ResourceData, m interf if !ok { poolName := d.Get("name").(string) clusterID := regional.ExpandID(d.Get("cluster_id")) + res, err := k8sAPI.ListPools(&k8s.ListPoolsRequest{ Region: region, Name: types.ExpandStringPtr(poolName), diff --git a/internal/services/k8s/pool_data_source_test.go b/internal/services/k8s/pool_data_source_test.go index a754a662e3..ec1193c67d 100644 --- a/internal/services/k8s/pool_data_source_test.go +++ b/internal/services/k8s/pool_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccDataSourcePool_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + clusterName := "tf-cluster-pool" poolName := "tf-pool" version := testAccK8SClusterGetLatestK8SVersion(tt) diff --git a/internal/services/k8s/pool_test.go b/internal/services/k8s/pool_test.go index 2e446724db..0d1bc3fac5 100644 --- a/internal/services/k8s/pool_test.go +++ b/internal/services/k8s/pool_test.go @@ -531,6 +531,7 @@ func testAccCheckK8SPoolServersAreInPrivateNetwork(tt *acctest.TestTools, cluste if !ok { return fmt.Errorf("resource not found: %s", clusterTFName) } + k8sAPI, region, clusterID, err := k8s.NewAPIWithRegionAndID(tt.Meta, rs.Primary.ID) if err != nil { return err @@ -540,6 +541,7 @@ func testAccCheckK8SPoolServersAreInPrivateNetwork(tt *acctest.TestTools, cluste if !ok { return fmt.Errorf("resource not found: %s", poolTFName) } + _, _, poolID, err := k8s.NewAPIWithRegionAndID(tt.Meta, rs.Primary.ID) if err != nil { return err @@ -549,6 +551,7 @@ func testAccCheckK8SPoolServersAreInPrivateNetwork(tt *acctest.TestTools, cluste if !ok { return fmt.Errorf("resource not found: %s", pnTFName) } + _, _, pnID, err := vpc.NewAPIWithRegionAndID(tt.Meta, rs.Primary.ID) if err != nil { return err @@ -581,11 +584,13 @@ func testAccCheckK8SPoolServersAreInPrivateNetwork(tt *acctest.TestTools, cluste } pnfound := false + for _, privateNic := range server.Server.PrivateNics { if privateNic.PrivateNetworkID == pnID { pnfound = true } } + if pnfound == false { return fmt.Errorf("node %s is not in linked to private network %s", node.ID, pnID) } @@ -601,6 +606,7 @@ func testAccCheckK8SPoolPublicIP(tt *acctest.TestTools, clusterTFName, poolTFNam if !ok { return fmt.Errorf("resource not found: %s", clusterTFName) } + k8sAPI, region, clusterID, err := k8s.NewAPIWithRegionAndID(tt.Meta, rs.Primary.ID) if err != nil { return err @@ -610,6 +616,7 @@ func testAccCheckK8SPoolPublicIP(tt *acctest.TestTools, clusterTFName, poolTFNam if !ok { return fmt.Errorf("resource not found: %s", poolTFName) } + _, _, poolID, err := k8s.NewAPIWithRegionAndID(tt.Meta, rs.Primary.ID) if err != nil { return err @@ -644,6 +651,7 @@ func testAccCheckK8SPoolPublicIP(tt *acctest.TestTools, clusterTFName, poolTFNam if disabled == true && server.Server.PublicIPs != nil && len(server.Server.PublicIPs) > 0 { return errors.New("found node with public IP when none was expected") } + if disabled == false && len(server.Server.PublicIPs) == 0 { return errors.New("found node with no public IP when one was expected") } @@ -976,14 +984,17 @@ func testAccCheckK8SPoolNodesOneOfIsDeleting(name string) resource.TestCheckFunc if !ok { return fmt.Errorf("resource not found: %s", name) } + nodesZeroStatus, ok := rs.Primary.Attributes["nodes.0.status"] if !ok { return errors.New("attribute \"nodes.0.status\" was not set") } + nodesOneStatus, ok := rs.Primary.Attributes["nodes.1.status"] if !ok { return errors.New("attribute \"nodes.1.status\" was not set") } + if nodesZeroStatus == "ready" && nodesOneStatus == "deleting" || nodesZeroStatus == "deleting" && nodesOneStatus == "ready" { return nil diff --git a/internal/services/k8s/testfuncs/sweep.go b/internal/services/k8s/testfuncs/sweep.go index 4438996aa7..022f94d9e9 100644 --- a/internal/services/k8s/testfuncs/sweep.go +++ b/internal/services/k8s/testfuncs/sweep.go @@ -22,6 +22,7 @@ func testSweepK8SCluster(_ string) error { k8sAPI := k8sSDK.NewAPI(scwClient) logging.L.Debugf("sweeper: destroying the k8s cluster in (%s)", region) + listClusters, err := k8sAPI.ListClusters(&k8sSDK.ListClustersRequest{Region: region}, scw.WithAllPages()) if err != nil { return fmt.Errorf("error listing clusters in (%s) in sweeper: %s", region, err) @@ -46,6 +47,7 @@ func testSweepK8SCluster(_ string) error { return fmt.Errorf("error deleting pool in sweeper: %s", err) } } + _, err = k8sAPI.DeleteCluster(&k8sSDK.DeleteClusterRequest{ Region: region, ClusterID: cluster.ID, diff --git a/internal/services/k8s/types.go b/internal/services/k8s/types.go index e1f00fcfe1..c268ca370a 100644 --- a/internal/services/k8s/types.go +++ b/internal/services/k8s/types.go @@ -26,6 +26,7 @@ func clusterAutoscalerConfigFlatten(cluster *k8s.Cluster) []map[string]interface // should never happen return nil } + autoscalerConfig["scale_down_utilization_threshold"] = thresholdF64 autoscalerConfig["max_graceful_termination_sec"] = cluster.AutoscalerConfig.MaxGracefulTerminationSec diff --git a/internal/services/lb/acls_data_source.go b/internal/services/lb/acls_data_source.go index aac15849a5..bbb8a2d640 100644 --- a/internal/services/lb/acls_data_source.go +++ b/internal/services/lb/acls_data_source.go @@ -156,6 +156,7 @@ func DataSourceLbACLsRead(ctx context.Context, d *schema.ResourceData, m interfa } acls := []interface{}(nil) + for _, acl := range res.ACLs { rawACL := make(map[string]interface{}) rawACL["id"] = zonal.NewIDString(zone, acl.ID) diff --git a/internal/services/lb/backend.go b/internal/services/lb/backend.go index 08a68f331b..2c5dc3eebc 100644 --- a/internal/services/lb/backend.go +++ b/internal/services/lb/backend.go @@ -351,22 +351,27 @@ func resourceLbBackendCreate(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } + healthCheckDelay, err := types.ExpandDuration(d.Get("health_check_delay")) if err != nil { return diag.FromErr(err) } + timeoutServer, err := types.ExpandDuration(d.Get("timeout_server")) if err != nil { return diag.FromErr(err) } + timeoutConnect, err := types.ExpandDuration(d.Get("timeout_connect")) if err != nil { return diag.FromErr(err) } + timeoutTunnel, err := types.ExpandDuration(d.Get("timeout_tunnel")) if err != nil { return diag.FromErr(err) } + createReq := &lbSDK.ZonedAPICreateBackendRequest{ Zone: zone, LBID: lbID, @@ -400,24 +405,30 @@ func resourceLbBackendCreate(ctx context.Context, d *schema.ResourceData, m inte if maxConn, ok := d.GetOk("max_connections"); ok { createReq.MaxConnections = types.ExpandInt32Ptr(maxConn) } + if timeoutQueue, ok := d.GetOk("timeout_queue"); ok { timeout, err := time.ParseDuration(timeoutQueue.(string)) if err != nil { return diag.FromErr(err) } + createReq.TimeoutQueue = &scw.Duration{Seconds: int64(timeout.Seconds())} } + if redispatchAttemptCount, ok := d.GetOk("redispatch_attempt_count"); ok { createReq.RedispatchAttemptCount = types.ExpandInt32Ptr(redispatchAttemptCount) } + if maxRetries, ok := d.GetOk("max_retries"); ok { createReq.MaxRetries = types.ExpandInt32Ptr(maxRetries) } + if healthCheckTransientDelay, ok := d.GetOk("health_check_transient_delay"); ok { timeout, err := time.ParseDuration(healthCheckTransientDelay.(string)) if err != nil { return diag.FromErr(err) } + createReq.HealthCheck.TransientCheckDelay = &scw.Duration{Seconds: int64(timeout.Seconds()), Nanos: int32(timeout.Nanoseconds())} } @@ -539,10 +550,12 @@ func resourceLbBackendUpdate(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } + timeoutConnect, err := types.ExpandDuration(d.Get("timeout_connect")) if err != nil { return diag.FromErr(err) } + timeoutTunnel, err := types.ExpandDuration(d.Get("timeout_tunnel")) if err != nil { return diag.FromErr(err) @@ -575,6 +588,7 @@ func resourceLbBackendUpdate(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } + req.TimeoutQueue = &scw.Duration{Seconds: int64(timeoutQueueParsed.Seconds())} } @@ -590,6 +604,7 @@ func resourceLbBackendUpdate(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } + healthCheckDelay, err := types.ExpandDuration(d.Get("health_check_delay")) if err != nil { return diag.FromErr(err) @@ -606,11 +621,13 @@ func resourceLbBackendUpdate(ctx context.Context, d *schema.ResourceData, m inte HTTPSConfig: expandLbHCHTTPS(d.Get("health_check_https")), CheckSendProxy: d.Get("health_check_send_proxy").(bool), } + if healthCheckTransientDelay, ok := d.GetOk("health_check_transient_delay"); ok { timeout, err := time.ParseDuration(healthCheckTransientDelay.(string)) if err != nil { return diag.FromErr(err) } + updateHCRequest.TransientCheckDelay = &scw.Duration{Seconds: int64(timeout.Seconds()), Nanos: int32(timeout.Nanoseconds())} } diff --git a/internal/services/lb/backend_data_source.go b/internal/services/lb/backend_data_source.go index e480dc8f98..58b8100660 100644 --- a/internal/services/lb/backend_data_source.go +++ b/internal/services/lb/backend_data_source.go @@ -44,6 +44,7 @@ func DataSourceLbBackendRead(ctx context.Context, d *schema.ResourceData, m inte backID, ok := d.GetOk("backend_id") if !ok { // Get LB by name. backendName := d.Get("name").(string) + res, err := api.ListBackends(&lbSDK.ZonedAPIListBackendsRequest{ Zone: zone, Name: types.ExpandStringPtr(backendName), @@ -64,8 +65,10 @@ func DataSourceLbBackendRead(ctx context.Context, d *schema.ResourceData, m inte backID = foundBackend.ID } + zonedID := datasource.NewZonedID(backID, zone) d.SetId(zonedID) + err = d.Set("backend_id", zonedID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/lb/backends_data_source.go b/internal/services/lb/backends_data_source.go index d2e2cf77f2..7cc30ce104 100644 --- a/internal/services/lb/backends_data_source.go +++ b/internal/services/lb/backends_data_source.go @@ -216,6 +216,7 @@ func DataSourceLbBackendsRead(ctx context.Context, d *schema.ResourceData, m int } backends := []interface{}(nil) + for _, backend := range res.Backends { rawBackend := make(map[string]interface{}) rawBackend["id"] = zonal.NewID(zone, backend.ID).String() diff --git a/internal/services/lb/certificate.go b/internal/services/lb/certificate.go index 3ab154a9fe..3203940c97 100644 --- a/internal/services/lb/certificate.go +++ b/internal/services/lb/certificate.go @@ -220,6 +220,7 @@ func resourceLbCertificateRead(ctx context.Context, d *schema.ResourceData, m in if certificate.StatusDetails != nil { errDetails = *certificate.StatusDetails } + diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, Summary: fmt.Sprintf("certificate %s with error state", certificate.ID), @@ -252,6 +253,7 @@ func resourceLbCertificateUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + if err != nil { if httperrors.Is403(err) { d.SetId("") diff --git a/internal/services/lb/certificate_data_source.go b/internal/services/lb/certificate_data_source.go index 32400da1f5..63c7e60132 100644 --- a/internal/services/lb/certificate_data_source.go +++ b/internal/services/lb/certificate_data_source.go @@ -44,6 +44,7 @@ func DataSourceLbCertificateRead(ctx context.Context, d *schema.ResourceData, m crtID, ok := d.GetOk("certificate_id") if !ok { // Get LB by name. certificateName := d.Get("name").(string) + res, err := api.ListCertificates(&lbSDK.ZonedAPIListCertificatesRequest{ Zone: zone, Name: types.ExpandStringPtr(certificateName), @@ -64,8 +65,10 @@ func DataSourceLbCertificateRead(ctx context.Context, d *schema.ResourceData, m crtID = foundCertificate.ID } + zonedID := datasource.NewZonedID(crtID, zone) d.SetId(zonedID) + err = d.Set("certificate_id", zonedID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/lb/data_source_lb_frontends.go b/internal/services/lb/data_source_lb_frontends.go index b44071e0d5..3b08f7f289 100644 --- a/internal/services/lb/data_source_lb_frontends.go +++ b/internal/services/lb/data_source_lb_frontends.go @@ -105,6 +105,7 @@ func DataSourceLbFrontendsRead(ctx context.Context, d *schema.ResourceData, m in } frontends := []interface{}(nil) + for _, frontend := range res.Frontends { rawFrontend := make(map[string]interface{}) rawFrontend["id"] = zonal.NewIDString(zone, frontend.ID) @@ -116,6 +117,7 @@ func DataSourceLbFrontendsRead(ctx context.Context, d *schema.ResourceData, m in rawFrontend["backend_id"] = frontend.Backend.ID rawFrontend["timeout_client"] = types.FlattenDuration(frontend.TimeoutClient) rawFrontend["enable_http3"] = frontend.EnableHTTP3 + if len(frontend.CertificateIDs) > 0 { rawFrontend["certificate_ids"] = frontend.CertificateIDs } diff --git a/internal/services/lb/data_source_lb_ip.go b/internal/services/lb/data_source_lb_ip.go index b259fef2c0..71c3657047 100644 --- a/internal/services/lb/data_source_lb_ip.go +++ b/internal/services/lb/data_source_lb_ip.go @@ -59,17 +59,21 @@ func DataSourceLbIPRead(ctx context.Context, d *schema.ResourceData, m interface if err != nil { return diag.FromErr(err) } + if len(res.IPs) == 0 { return diag.FromErr(fmt.Errorf("no ips found with the address %s", d.Get("ip_address"))) } + if len(res.IPs) > 1 { return diag.FromErr(fmt.Errorf("%d ips found with the same address %s", len(res.IPs), d.Get("ip_address"))) } + ipID = res.IPs[0].ID } zoneID := datasource.NewZonedID(ipID, zone) d.SetId(zoneID) + err = d.Set("ip_id", zoneID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/lb/frontend.go b/internal/services/lb/frontend.go index 3535622adc..af0279c2a8 100644 --- a/internal/services/lb/frontend.go +++ b/internal/services/lb/frontend.go @@ -350,6 +350,7 @@ func flattenLBACLs(acls []*lbSDK.ACL) interface{} { sort.Slice(acls, func(i, j int) bool { return acls[i].Index < acls[j].Index }) + rawACLs := make([]interface{}, 0, len(acls)) for _, apiACL := range acls { rawACLs = append(rawACLs, flattenLbACL(apiACL)) @@ -367,6 +368,7 @@ func resourceLbFrontendUpdateACL(ctx context.Context, d *schema.ResourceData, lb if err != nil { return diag.FromErr(err) } + apiACLs := make(map[int32]*lbSDK.ACL) for _, acl := range resACL.ACLs { apiACLs[acl.Index] = acl @@ -390,6 +392,7 @@ func resourceLbFrontendUpdateACL(ctx context.Context, d *schema.ResourceData, lb if ACLEquals(stateACL, apiACL) { continue } + _, err = lbAPI.UpdateACL(&lbSDK.ZonedAPIUpdateACLRequest{ Zone: zone, ACLID: apiACL.ID, @@ -434,6 +437,7 @@ func resourceLbFrontendUpdateACL(ctx context.Context, d *schema.ResourceData, lb func expandsLBACLs(raw interface{}) []*lbSDK.ACL { d := raw.([]interface{}) newACL := make([]*lbSDK.ACL, 0) + for _, rawACL := range d { newACL = append(newACL, expandLbACL(rawACL)) } @@ -468,6 +472,7 @@ func resourceLbFrontendUpdate(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + req := &lbSDK.ZonedAPIUpdateFrontendRequest{ Zone: zone, FrontendID: ID, @@ -523,9 +528,11 @@ func ACLEquals(aclA, aclB *lbSDK.ACL) bool { if aclA.Name != aclB.Name { return false } + if !cmp.Equal(aclA.Match, aclB.Match) { return false } + if !cmp.Equal(aclA.Action, aclB.Action) { return false } diff --git a/internal/services/lb/frontend_data_source.go b/internal/services/lb/frontend_data_source.go index 04212b7cb6..645141bed3 100644 --- a/internal/services/lb/frontend_data_source.go +++ b/internal/services/lb/frontend_data_source.go @@ -44,6 +44,7 @@ func DataSourceLbFrontendRead(ctx context.Context, d *schema.ResourceData, m int frontID, ok := d.GetOk("frontend_id") if !ok { // Get LB by name. frontName := d.Get("name").(string) + res, err := api.ListFrontends(&lbSDK.ZonedAPIListFrontendsRequest{ Zone: zone, Name: types.ExpandStringPtr(frontName), @@ -64,8 +65,10 @@ func DataSourceLbFrontendRead(ctx context.Context, d *schema.ResourceData, m int frontID = foundFront.ID } + zonedID := datasource.NewZonedID(frontID, zone) d.SetId(zonedID) + err = d.Set("frontend_id", zonedID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/lb/frontend_test.go b/internal/services/lb/frontend_test.go index 77768f9a63..990ac0aafc 100644 --- a/internal/services/lb/frontend_test.go +++ b/internal/services/lb/frontend_test.go @@ -582,6 +582,7 @@ func isACLCorrect(tt *acctest.TestTools, frontendName string, expectedAcls []*lb if _, found := aclMap[int32(i)]; !found { return fmt.Errorf("cannot find an index set [%d]", i) } + if !testCompareAcls(*expectedAcls[i-1], *aclMap[int32(i)]) { return fmt.Errorf("two acls are not equal on stage %d", i) } diff --git a/internal/services/lb/helpers_lb.go b/internal/services/lb/helpers_lb.go index 8db946fdda..e3ad22aed0 100644 --- a/internal/services/lb/helpers_lb.go +++ b/internal/services/lb/helpers_lb.go @@ -55,12 +55,15 @@ func IsPrivateNetworkEqual(a, b *lbSDK.PrivateNetwork) bool { if a == nil || b == nil { return a == b } + if a.PrivateNetworkID != b.PrivateNetworkID { return false } + if !reflect.DeepEqual(a.DHCPConfig, b.DHCPConfig) { //nolint:staticcheck return false } + if !reflect.DeepEqual(a.StaticConfig, b.StaticConfig) { //nolint:staticcheck return false } @@ -114,6 +117,7 @@ func UpgradeStateV1Func(_ context.Context, rawState map[string]interface{}, _ in if !exist { return nil, errors.New("upgrade: id not exist") } + rawState["id"], err = lbUpgradeV1RegionalToZonedID(ID.(string)) if err != nil { return nil, err diff --git a/internal/services/lb/ip.go b/internal/services/lb/ip.go index d818ebbdb4..7d8bd57c72 100644 --- a/internal/services/lb/ip.go +++ b/internal/services/lb/ip.go @@ -155,12 +155,14 @@ func resourceLbIPRead(ctx context.Context, d *schema.ResourceData, m interface{} _ = d.Set("tags", ip.Tags) isIPv6 := false + if ip.IPAddress != "" { parsedIP := net.ParseIP(ip.IPAddress) if parsedIP != nil && parsedIP.To4() == nil { isIPv6 = true } } + _ = d.Set("is_ipv6", isIPv6) return nil @@ -173,6 +175,7 @@ func resourceLbIPUpdate(ctx context.Context, d *schema.ResourceData, m interface } var ip *lbSDK.IP + err = retry.RetryContext(ctx, d.Timeout(schema.TimeoutUpdate), func() *retry.RetryError { res, errGet := lbAPI.GetIP(&lbSDK.ZonedAPIGetIPRequest{ Zone: zone, @@ -261,6 +264,7 @@ func resourceLbIPDelete(ctx context.Context, d *schema.ResourceData, m interface } var ip *lbSDK.IP + err = retry.RetryContext(ctx, d.Timeout(schema.TimeoutDelete), func() *retry.RetryError { res, errGet := lbAPI.GetIP(&lbSDK.ZonedAPIGetIPRequest{ Zone: zone, diff --git a/internal/services/lb/ips_data_source.go b/internal/services/lb/ips_data_source.go index c4c24d1a05..4d900a5f7c 100644 --- a/internal/services/lb/ips_data_source.go +++ b/internal/services/lb/ips_data_source.go @@ -88,6 +88,7 @@ func DataSourceLbIPsRead(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } + res, err := lbAPI.ListIPs(&lb.ZonedAPIListIPsRequest{ Zone: zone, ProjectID: types.ExpandStringPtr(d.Get("project_id")), @@ -99,6 +100,7 @@ func DataSourceLbIPsRead(ctx context.Context, d *schema.ResourceData, m interfac } var filteredList []*lb.IP + if cidrRange, ok := d.GetOk("ip_cidr_range"); ok { for i := range res.IPs { if ipv4Match(cidrRange.(string), res.IPs[i].IPAddress) { @@ -110,6 +112,7 @@ func DataSourceLbIPsRead(ctx context.Context, d *schema.ResourceData, m interfac } ips := []interface{}(nil) + for _, ip := range filteredList { rawIP := make(map[string]interface{}) rawIP["id"] = zonal.NewID(ip.Zone, ip.ID).String() @@ -119,6 +122,7 @@ func DataSourceLbIPsRead(ctx context.Context, d *schema.ResourceData, m interfac rawIP["zone"] = string(zone) rawIP["organization_id"] = ip.OrganizationID rawIP["project_id"] = ip.ProjectID + if len(ip.Tags) > 0 { rawIP["tags"] = ip.Tags } diff --git a/internal/services/lb/lb.go b/internal/services/lb/lb.go index dbf1c3bc12..2aa68c2480 100644 --- a/internal/services/lb/lb.go +++ b/internal/services/lb/lb.go @@ -300,10 +300,13 @@ func resourceLbRead(ctx context.Context, d *schema.ResourceData, m interface{}) // For now API return lowercase lb type. This should be fixed in a near future on the API side _ = d.Set("type", strings.ToUpper(lb.Type)) _ = d.Set("ssl_compatibility_level", lb.SslCompatibilityLevel.String()) + if len(lb.IP) > 0 { _ = d.Set("ip_id", zonal.NewIDString(zone, lb.IP[0].ID)) _ = d.Set("ip_ids", flattenLBIPIDs(zone, lb.IP)) + var ipv4Address, ipv6Address string + for _, ip := range lb.IP { parsedIP := net.ParseIP(ip.IPAddress) if parsedIP != nil { @@ -314,6 +317,7 @@ func resourceLbRead(ctx context.Context, d *schema.ResourceData, m interface{}) } } } + _ = d.Set("ip_address", ipv4Address) _ = d.Set("ipv6_address", ipv6Address) } @@ -327,6 +331,7 @@ func resourceLbRead(ctx context.Context, d *schema.ResourceData, m interface{}) return diag.FromErr(err) } + _ = d.Set("private_network", flattenPrivateNetworkConfigs(privateNetworks)) return nil @@ -401,6 +406,7 @@ func resourceLbUpdate(ctx context.Context, d *schema.ResourceData, m interface{} for id := range oldIPIDsSet { ipv4ID = id } + for id := range newIPIDsSet { if id != ipv4ID { ipv6ID = id @@ -482,10 +488,12 @@ func resourceLbUpdate(ctx context.Context, d *schema.ResourceData, m interface{} } oldPNs, newPNs := d.GetChange("private_network") + oldPNConfigs, err := expandPrivateNetworks(oldPNs) if err != nil { return diag.FromErr(err) } + newPNConfigs, err := expandPrivateNetworks(newPNs) if err != nil { return diag.FromErr(err) @@ -528,6 +536,7 @@ func resourceLbUpdate(ctx context.Context, d *schema.ResourceData, m interface{} for _, pn := range privateNetworks { tflog.Debug(ctx, fmt.Sprintf("PrivateNetwork ID %s state: %v", pn.PrivateNetworkID, pn.Status)) + if pn.Status == lbSDK.PrivateNetworkStatusError { err = lbAPI.DetachPrivateNetwork(&lbSDK.ZonedAPIDetachPrivateNetworkRequest{ Zone: zone, diff --git a/internal/services/lb/lb_data_source.go b/internal/services/lb/lb_data_source.go index b926dee16c..bdecd56aa3 100644 --- a/internal/services/lb/lb_data_source.go +++ b/internal/services/lb/lb_data_source.go @@ -49,6 +49,7 @@ func DataSourceLbRead(ctx context.Context, d *schema.ResourceData, m interface{} lbID, ok := d.GetOk("lb_id") if !ok { // Get LB by name. lbName := d.Get("name").(string) + res, err := api.ListLBs(&lbSDK.ZonedAPIListLBsRequest{ Zone: zone, Name: types.ExpandStringPtr(lbName), @@ -74,8 +75,10 @@ func DataSourceLbRead(ctx context.Context, d *schema.ResourceData, m interface{} if err != nil { return diag.FromErr(err) } + zonedID := datasource.NewZonedID(lbID, zone) d.SetId(zonedID) + err = d.Set("lb_id", zonedID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/lb/lbs_data_source.go b/internal/services/lb/lbs_data_source.go index 332b26f8fa..ccce7fe064 100644 --- a/internal/services/lb/lbs_data_source.go +++ b/internal/services/lb/lbs_data_source.go @@ -167,6 +167,7 @@ func DataSourceLbsRead(ctx context.Context, d *schema.ResourceData, m interface{ if err != nil { return diag.FromErr(err) } + res, err := lbAPI.ListLBs(&lb.ZonedAPIListLBsRequest{ Zone: zone, Name: types.ExpandStringPtr(d.Get("name")), @@ -178,6 +179,7 @@ func DataSourceLbsRead(ctx context.Context, d *schema.ResourceData, m interface{ } lbs := []interface{}(nil) + for _, loadbalancer := range res.LBs { rawLb := make(map[string]interface{}) rawLb["id"] = zonal.NewID(loadbalancer.Zone, loadbalancer.ID).String() diff --git a/internal/services/lb/route_data_source.go b/internal/services/lb/route_data_source.go index b59708c123..a253ebcc6d 100644 --- a/internal/services/lb/route_data_source.go +++ b/internal/services/lb/route_data_source.go @@ -36,6 +36,7 @@ func DataSourceLbRouteRead(ctx context.Context, d *schema.ResourceData, m interf zonedID := datasource.NewZonedID(routeID, zone) d.SetId(zonedID) + err = d.Set("route_id", zonedID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/lb/routes_data_source.go b/internal/services/lb/routes_data_source.go index 6ed0fd6d44..789bdea68d 100644 --- a/internal/services/lb/routes_data_source.go +++ b/internal/services/lb/routes_data_source.go @@ -84,6 +84,7 @@ func DataSourceLbRoutesRead(ctx context.Context, d *schema.ResourceData, m inter } routes := []interface{}(nil) + for _, route := range res.Routes { rawRoute := make(map[string]interface{}) rawRoute["id"] = zonal.NewID(zone, route.ID).String() diff --git a/internal/services/lb/testfuncs/sweep.go b/internal/services/lb/testfuncs/sweep.go index 9e2303a073..8dd8d23e1f 100644 --- a/internal/services/lb/testfuncs/sweep.go +++ b/internal/services/lb/testfuncs/sweep.go @@ -29,6 +29,7 @@ func testSweepLB(_ string) error { lbAPI := lbSDK.NewZonedAPI(scwClient) logging.L.Debugf("sweeper: destroying the lbs in (%s)", zone) + listLBs, err := lbAPI.ListLBs(&lbSDK.ZonedAPIListLBsRequest{ Zone: zone, }, scw.WithAllPages()) @@ -52,6 +53,7 @@ func testSweepLB(_ string) error { if err != nil { return fmt.Errorf("error waiting for lb in sweeper: %s", err) } + err = lbAPI.DeleteLB(&lbSDK.ZonedAPIDeleteLBRequest{ LBID: l.ID, ReleaseIP: true, @@ -71,6 +73,7 @@ func testSweepIP(_ string) error { lbAPI := lbSDK.NewZonedAPI(scwClient) logging.L.Debugf("sweeper: destroying the lb ips in zone (%s)", zone) + listIPs, err := lbAPI.ListIPs(&lbSDK.ZonedAPIListIPsRequest{Zone: zone}, scw.WithAllPages()) if err != nil { return fmt.Errorf("error listing lb ips in (%s) in sweeper: %s", zone, err) diff --git a/internal/services/lb/types.go b/internal/services/lb/types.go index 413992dbe5..7ed645be31 100644 --- a/internal/services/lb/types.go +++ b/internal/services/lb/types.go @@ -24,15 +24,19 @@ func flattenPrivateNetworkConfigs(privateNetworks []*lb.PrivateNetwork) interfac } pnI := []map[string]interface{}(nil) + var dhcpConfigExist bool + for _, pn := range privateNetworks { if pn.DHCPConfig != nil { //nolint:staticcheck dhcpConfigExist = true } + pnRegion, err := pn.LB.Zone.Region() if err != nil { return diag.FromErr(err) } + pnRegionalID := regional.NewIDString(pnRegion, pn.PrivateNetworkID) pnI = append(pnI, map[string]interface{}{ "private_network_id": pnRegionalID, @@ -67,6 +71,7 @@ func expandLbACLMatch(raw interface{}) *lb.ACLMatch { if raw == nil || len(raw.([]interface{})) != 1 { return nil } + rawMap := raw.([]interface{})[0].(map[string]interface{}) // scaleway api require ip subnet, so if we did not specify one, just put 0.0.0.0/0 instead @@ -153,6 +158,7 @@ func expandLbHCHTTP(raw interface{}) *lb.HealthCheckHTTPConfig { if raw == nil || len(raw.([]interface{})) != 1 { return nil } + rawMap := raw.([]interface{})[0].(map[string]interface{}) return &lb.HealthCheckHTTPConfig{ @@ -205,6 +211,7 @@ func expandLbLetsEncrypt(raw interface{}) *lb.CreateCertificateRequestLetsencryp config := &lb.CreateCertificateRequestLetsencryptConfig{ CommonName: rawMap["common_name"].(string), } + for _, alternativeName := range alternativeNames { config.SubjectAlternativeName = append(config.SubjectAlternativeName, alternativeName.(string)) } @@ -288,6 +295,7 @@ func expandLbACLAction(raw interface{}) *lb.ACLAction { if raw == nil || len(raw.([]interface{})) != 1 { return nil } + rawMap := raw.([]interface{})[0].(map[string]interface{}) return &lb.ACLAction{ @@ -314,6 +322,7 @@ func expandLbACLActionRedirect(raw interface{}) *lb.ACLActionRedirect { if raw == nil || len(raw.([]interface{})) != 1 { return nil } + rawMap := raw.([]interface{})[0].(map[string]interface{}) return &lb.ACLActionRedirect{ @@ -334,11 +343,13 @@ func expandPrivateNetworks(data interface{}) ([]*lb.PrivateNetwork, error) { rawPn := pn.(map[string]interface{}) privateNetwork := &lb.PrivateNetwork{} privateNetwork.PrivateNetworkID = locality.ExpandID(rawPn["private_network_id"].(string)) + if staticConfig, hasStaticConfig := rawPn["static_config"]; hasStaticConfig && len(staticConfig.([]interface{})) > 0 { privateNetwork.StaticConfig = expandLbPrivateNetworkStaticConfig(staticConfig) //nolint:staticcheck } else { privateNetwork.DHCPConfig = expandLbPrivateNetworkDHCPConfig(rawPn["dhcp_config"]) //nolint:staticcheck } + privateNetwork.IpamIDs = locality.ExpandIDs(rawPn["ipam_ids"]) pns = append(pns, privateNetwork) @@ -404,6 +415,7 @@ func attachLBPrivateNetworks(ctx context.Context, lbAPI *lb.ZonedAPI, zone scw.Z if err != nil && !httperrors.Is404(err) { return nil, err } + tflog.Debug(ctx, fmt.Sprintf("DHCP config: %v", pn.DHCPConfig)) //nolint:staticcheck tflog.Debug(ctx, fmt.Sprintf("Static config: %v", pn.StaticConfig)) //nolint:staticcheck @@ -419,6 +431,7 @@ func flattenLbInstances(instances []*lb.Instance) interface{} { if instances == nil { return nil } + flattenedInstances := []map[string]interface{}(nil) for _, instance := range instances { flattenedInstances = append(flattenedInstances, map[string]interface{}{ @@ -438,6 +451,7 @@ func flattenLbIPs(ips []*lb.IP) interface{} { if ips == nil { return nil } + flattenedIPs := []map[string]interface{}(nil) for _, ip := range ips { flattenedIPs = append(flattenedIPs, map[string]interface{}{ @@ -458,6 +472,7 @@ func flattenLBIPIDs(zone scw.Zone, ips []*lb.IP) []string { if ips == nil { return nil } + flattenedIPs := make([]string, len(ips)) for i, ip := range ips { flattenedIPs[i] = zonal.NewIDString(zone, ip.ID) diff --git a/internal/services/mnq/helpers_mnq.go b/internal/services/mnq/helpers_mnq.go index 7a455a0260..91065c41a0 100644 --- a/internal/services/mnq/helpers_mnq.go +++ b/internal/services/mnq/helpers_mnq.go @@ -21,6 +21,7 @@ const ( func newMNQNatsAPI(d *schema.ResourceData, m interface{}) (*mnq.NatsAPI, scw.Region, error) { api := mnq.NewNatsAPI(meta.ExtractScwClient(m)) + region, err := meta.ExtractRegion(d, m) if err != nil { return nil, "", err @@ -130,13 +131,16 @@ func decomposeARN(arn string) (*ARN, error) { if elems[0] != "arn" { return nil, fmt.Errorf("expected part 0 to be \"arn\", got %q", elems[0]) } + if elems[1] != "scw" { return nil, fmt.Errorf("expected part 1 to be \"scw\", got %q", elems[1]) } + region, err := scw.ParseRegion(elems[3]) if err != nil { return nil, fmt.Errorf("expected part 2 to be a valid region: %w", err) } + projectID, found := strings.CutPrefix(elems[4], "project-") if !found { return nil, errors.New("expected part 3 to have format \"project-{uuid}\"") @@ -212,6 +216,7 @@ func awsResourceDataToAttribute(awsAttributes map[string]string, awsAttribute st } var s string + switch resourceSchema.Type { case schema.TypeBool: s = strconv.FormatBool(resourceValue.(bool)) diff --git a/internal/services/mnq/helpers_mnq_queue.go b/internal/services/mnq/helpers_mnq_queue.go index 8e0fa96a3b..c08cf30308 100644 --- a/internal/services/mnq/helpers_mnq_queue.go +++ b/internal/services/mnq/helpers_mnq_queue.go @@ -95,6 +95,7 @@ func NATSClientWithRegion( //nolint:ireturn,nolintlint endpoint := d.Get("endpoint").(string) creds := d.Get("credentials").(string) + js, err := newNATSJetStreamClient(region.String(), endpoint, creds) if err != nil { return nil, "", err @@ -178,6 +179,7 @@ func resourceMNQQueueName(name interface{}, prefix interface{}, isSQS bool, isSQ } else { output = types.NewRandomName("queue") } + if isSQS && isSQSFifo { return output + SQSFIFOQueueNameSuffix } diff --git a/internal/services/mnq/helpers_mnq_sns.go b/internal/services/mnq/helpers_mnq_sns.go index c358750010..7270830140 100644 --- a/internal/services/mnq/helpers_mnq_sns.go +++ b/internal/services/mnq/helpers_mnq_sns.go @@ -42,6 +42,7 @@ func SNSClientWithRegionFromID(ctx context.Context, d *schema.ResourceData, m in if len(tab) != 2 { return nil, "", errors.New("invalid ID format, expected parts separated by slashes") } + region, err := scw.ParseRegion(tab[0]) if err != nil { return nil, "", fmt.Errorf("invalid region in id: %w", err) @@ -130,6 +131,7 @@ func resourceMNQSNSTopicName(name interface{}, prefix interface{}, isSQS bool, i } else { output = types.NewRandomName("topic") } + if isSQS && isSQSFifo { return output + SQSFIFOQueueNameSuffix } diff --git a/internal/services/mnq/sns_topic.go b/internal/services/mnq/sns_topic.go index 61bba75798..dc58ec9ed5 100644 --- a/internal/services/mnq/sns_topic.go +++ b/internal/services/mnq/sns_topic.go @@ -186,6 +186,7 @@ func ResourceMNQSNSTopicUpdate(ctx context.Context, d *schema.ResourceData, m in topicARN := ComposeSNSARN(region, projectID, topicName) changedAttributes := []string(nil) + for attributeName, schemaName := range SNSTopicAttributesToResourceMap { if d.HasChange(schemaName) { changedAttributes = append(changedAttributes, attributeName) diff --git a/internal/services/mnq/sns_topic_subscription_test.go b/internal/services/mnq/sns_topic_subscription_test.go index a3207ad24b..ecdb9fd7b8 100644 --- a/internal/services/mnq/sns_topic_subscription_test.go +++ b/internal/services/mnq/sns_topic_subscription_test.go @@ -18,6 +18,7 @@ import ( func TestAccSNSTopicSubscription_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/mnq/sqs_queue.go b/internal/services/mnq/sqs_queue.go index 3b0ab1f8b0..0ee1048a2a 100644 --- a/internal/services/mnq/sqs_queue.go +++ b/internal/services/mnq/sqs_queue.go @@ -168,6 +168,7 @@ func ResourceMNQSQSQueueCreate(ctx context.Context, d *schema.ResourceData, m in Attributes: attributes, QueueName: scw.StringPtr(queueName), } + _, err = transport.RetryWhenAWSErrCodeEquals(ctx, []string{AWSErrQueueDeletedRecently}, &transport.RetryWhenConfig[*sqs.CreateQueueOutput]{ Timeout: d.Timeout(schema.TimeoutCreate), Interval: defaultMNQQueueRetryInterval, diff --git a/internal/services/mnq/sqs_queue_test.go b/internal/services/mnq/sqs_queue_test.go index 5f3e144323..bb0abe2066 100644 --- a/internal/services/mnq/sqs_queue_test.go +++ b/internal/services/mnq/sqs_queue_test.go @@ -24,6 +24,7 @@ import ( func TestAccSQSQueue_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() resource.ParallelTest(t, resource.TestCase{ @@ -212,6 +213,7 @@ func isSQSQueueDestroyed(ctx context.Context, tt *acctest.TestTools) resource.Te // Project may have been deleted, check for it first // Checking for Queue first may lead to an AccessDenied if project has been deleted accountAPI := account.NewProjectAPI(tt.Meta) + _, err = accountAPI.GetProject(&accountSDK.ProjectAPIGetProjectRequest{ ProjectID: projectID, }) @@ -224,6 +226,7 @@ func isSQSQueueDestroyed(ctx context.Context, tt *acctest.TestTools) resource.Te } mnqAPI := mnqSDK.NewSqsAPI(tt.Meta.ScwClient()) + sqsInfo, err := mnqAPI.GetSqsInfo(&mnqSDK.SqsAPIGetSqsInfoRequest{ Region: region, ProjectID: projectID, diff --git a/internal/services/mnq/testfuncs/sweep.go b/internal/services/mnq/testfuncs/sweep.go index 621e1df872..9e9252a4ad 100644 --- a/internal/services/mnq/testfuncs/sweep.go +++ b/internal/services/mnq/testfuncs/sweep.go @@ -38,7 +38,9 @@ func AddTestSweepers() { func testSweepSQSCredentials(_ string) error { return acctest.SweepRegions((&mnqSDK.SqsAPI{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { mnqAPI := mnqSDK.NewSqsAPI(scwClient) + logging.L.Debugf("sweeper: destroying the mnq sqs credentials in (%s)", region) + listSqsCredentials, err := mnqAPI.ListSqsCredentials( &mnqSDK.SqsAPIListSqsCredentialsRequest{ Region: region, @@ -74,6 +76,7 @@ func testSweepSQS(_ string) error { if err != nil { return fmt.Errorf("failed to list projects: %w", err) } + for _, project := range listProjects.Projects { if !strings.HasPrefix(project.Name, "tf_tests") { continue @@ -97,7 +100,9 @@ func testSweepSQS(_ string) error { func testSweepSNSCredentials(_ string) error { return acctest.SweepRegions((&mnqSDK.SnsAPI{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { mnqAPI := mnqSDK.NewSnsAPI(scwClient) + logging.L.Debugf("sweeper: destroying the mnq sns credentials in (%s)", region) + listSnsCredentials, err := mnqAPI.ListSnsCredentials( &mnqSDK.SnsAPIListSnsCredentialsRequest{ Region: region, @@ -133,6 +138,7 @@ func testSweepSNS(_ string) error { if err != nil { return fmt.Errorf("failed to list projects: %w", err) } + for _, project := range listProjects.Projects { if !strings.HasPrefix(project.Name, "tf_tests") { continue @@ -156,7 +162,9 @@ func testSweepSNS(_ string) error { func testSweepNatsAccount(_ string) error { return acctest.SweepRegions((&mnqSDK.NatsAPI{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { mnqAPI := mnqSDK.NewNatsAPI(scwClient) + logging.L.Debugf("sweeper: destroying the mnq nats accounts in (%s)", region) + listNatsAccounts, err := mnqAPI.ListNatsAccounts( &mnqSDK.NatsAPIListNatsAccountsRequest{ Region: region, diff --git a/internal/services/mongodb/data_source_instance.go b/internal/services/mongodb/data_source_instance.go index 856ffc758f..dd7e122acf 100644 --- a/internal/services/mongodb/data_source_instance.go +++ b/internal/services/mongodb/data_source_instance.go @@ -43,6 +43,7 @@ func DataSourceInstanceRead(ctx context.Context, d *schema.ResourceData, m inter instanceID, ok := d.GetOk("instance_id") if !ok { instanceName := d.Get("name").(string) + res, err := mongodbAPI.ListInstances(&mongodb.ListInstancesRequest{ Region: region, Name: types.ExpandStringPtr(instanceName), @@ -66,6 +67,7 @@ func DataSourceInstanceRead(ctx context.Context, d *schema.ResourceData, m inter zonedID := datasource.NewZonedID(instanceID, zone) d.SetId(zonedID) + err = d.Set("instance_id", zonedID) if err != nil { return diag.FromErr(err) @@ -75,6 +77,7 @@ func DataSourceInstanceRead(ctx context.Context, d *schema.ResourceData, m inter Region: region, InstanceID: locality.ExpandID(instanceID.(string)), } + instance, err := mongodbAPI.GetInstance(getReq, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -104,6 +107,7 @@ func DataSourceInstanceRead(ctx context.Context, d *schema.ResourceData, m inter for _, setting := range instance.Settings { settingsMap[setting.Name] = setting.Value } + _ = d.Set("settings", settingsMap) } diff --git a/internal/services/mongodb/helpers.go b/internal/services/mongodb/helpers.go index 7bf386427c..c072ba70ae 100644 --- a/internal/services/mongodb/helpers.go +++ b/internal/services/mongodb/helpers.go @@ -41,6 +41,7 @@ func newAPIWithZoneAndRegion(d *schema.ResourceData, m interface{}) (*mongodb.AP if err != nil { return nil, "", "", err } + region, err := meta.ExtractRegion(d, m) if err != nil { return nil, "", "", err @@ -73,6 +74,7 @@ func NewAPIWithRegionAndID(m interface{}, id string) (*mongodb.API, scw.Region, if err != nil { return nil, "", "", err } + region, err := zone.Region() if err != nil { return nil, "", "", err diff --git a/internal/services/mongodb/instance.go b/internal/services/mongodb/instance.go index 715f829bb6..7cd2982935 100644 --- a/internal/services/mongodb/instance.go +++ b/internal/services/mongodb/instance.go @@ -172,7 +172,9 @@ func ResourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter nodeNumber := scw.Uint32Ptr(uint32(d.Get("node_number").(int))) snapshotID, exist := d.GetOk("snapshot_id") + var res *mongodb.Instance + if exist { volume := &mongodb.RestoreSnapshotRequestVolumeDetails{ VolumeType: mongodb.VolumeType(d.Get("volume_type").(string)), @@ -185,6 +187,7 @@ func ResourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter NodeType: d.Get("node_type").(string), Volume: volume, } + res, err = mongodbAPI.RestoreSnapshot(restoreSnapshotRequest, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -204,11 +207,13 @@ func ResourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter VolumeType: mongodb.VolumeType(d.Get("volume_type").(string)), } volumeSize, volumeSizeExist := d.GetOk("volume_size_in_gb") + if volumeSizeExist { volumeRequestDetails.VolumeSize = scw.Size(uint64(volumeSize.(int)) * uint64(scw.GB)) } else { volumeRequestDetails.VolumeSize = scw.Size(defaultVolumeSize * uint64(scw.GB)) } + createReq.Volume = volumeRequestDetails tags, tagsExist := d.GetOk("tags") @@ -226,7 +231,9 @@ func ResourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter return diag.FromErr(err) } } + d.SetId(zonal.NewIDString(zone, res.ID)) + _, err = waitForInstance(ctx, mongodbAPI, res.Region, res.ID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -281,6 +288,7 @@ func ResourceInstanceRead(ctx context.Context, d *schema.ResourceData, m interfa for _, setting := range instance.Settings { settingsMap[setting.Name] = setting.Value } + _ = d.Set("settings", settingsMap) } @@ -301,6 +309,7 @@ func ResourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m inter oldSizeInterface, newSizeInterface := d.GetChange("volume_size_in_gb") oldSize := uint64(oldSizeInterface.(int)) newSize := uint64(newSizeInterface.(int)) + if newSize < oldSize { return diag.FromErr(errors.New("volume_size_in_gb cannot be decreased")) } @@ -308,6 +317,7 @@ func ResourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m inter if newSize%5 != 0 { return diag.FromErr(errors.New("volume_size_in_gb must be a multiple of 5")) } + size := scw.Size(newSize * uint64(scw.GB)) upgradeInstanceRequests := mongodb.UpgradeInstanceRequest{ @@ -320,6 +330,7 @@ func ResourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m inter if err != nil { return diag.FromErr(err) } + _, err = waitForInstance(ctx, mongodbAPI, region, ID, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/mongodb/instance_test.go b/internal/services/mongodb/instance_test.go index 5d499bf046..b0c79f87b0 100644 --- a/internal/services/mongodb/instance_test.go +++ b/internal/services/mongodb/instance_test.go @@ -231,10 +231,12 @@ func IsInstanceDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { if err != nil { return err } + extractRegion, err := zone.Region() if err != nil { return err } + _, err = mongodbAPI.GetInstance(&mongodbSDK.GetInstanceRequest{ InstanceID: ID, Region: extractRegion, @@ -243,6 +245,7 @@ func IsInstanceDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { if err == nil { return fmt.Errorf("instance (%s) still exists", rs.Primary.ID) } + if !httperrors.Is404(err) { return err } diff --git a/internal/services/mongodb/snapshot.go b/internal/services/mongodb/snapshot.go index ac66d1c01c..ef29e21169 100644 --- a/internal/services/mongodb/snapshot.go +++ b/internal/services/mongodb/snapshot.go @@ -90,6 +90,7 @@ func ResourceSnapshotCreate(ctx context.Context, d *schema.ResourceData, m inter if err != nil { return diag.FromErr(err) } + instanceID := locality.ExpandID(d.Get("instance_id").(string)) createReq := &mongodb.CreateSnapshotRequest{ InstanceID: instanceID, @@ -102,8 +103,10 @@ func ResourceSnapshotCreate(ctx context.Context, d *schema.ResourceData, m inter if err != nil { return diag.FromErr(err) } + if snapshot != nil { d.SetId(zonal.NewIDString(zone, snapshot.ID)) + _, err = waitForSnapshot(ctx, mongodbAPI, region, instanceID, snapshot.ID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -118,16 +121,19 @@ func ResourceSnapshotRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + zone, snapshotID, err := zonal.ParseID(d.Id()) if err != nil { return diag.FromErr(err) } instanceID := locality.ExpandID(d.Get("instance_id").(string)) + snapshot, err := waitForSnapshot(ctx, mongodbAPI, region, instanceID, snapshotID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) } + _ = d.Set("instance_id", zonal.NewIDString(zone, snapshot.InstanceID)) _ = d.Set("name", snapshot.Name) _ = d.Set("instance_name", snapshot.InstanceName) @@ -147,6 +153,7 @@ func ResourceSnapshotUpdate(ctx context.Context, d *schema.ResourceData, m inter if err != nil { return diag.FromErr(err) } + _, snapshotID, err := zonal.ParseID(d.Id()) if err != nil { return diag.FromErr(err) @@ -192,6 +199,7 @@ func ResourceSnapshotDelete(_ context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + _, snapshotID, err := zonal.ParseID(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/services/mongodb/snapshot_test.go b/internal/services/mongodb/snapshot_test.go index 3459bf8d28..c6686a2cd0 100644 --- a/internal/services/mongodb/snapshot_test.go +++ b/internal/services/mongodb/snapshot_test.go @@ -121,6 +121,7 @@ func isSnapshotDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { if err == nil { return fmt.Errorf("instance (%s) still exists", rs.Primary.ID) } + if !httperrors.Is404(err) { return err } diff --git a/internal/services/mongodb/testfuncs/sweep.go b/internal/services/mongodb/testfuncs/sweep.go index 8d99e2df0a..8586e0317e 100644 --- a/internal/services/mongodb/testfuncs/sweep.go +++ b/internal/services/mongodb/testfuncs/sweep.go @@ -20,11 +20,14 @@ func AddTestSweepers() { func testSweepMongodbInstance(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { mongodbAPI := mongodb.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the mongodb instance in (%s)", zone) + extractRegion, err := zone.Region() if err != nil { return fmt.Errorf("error extract region in (%s) in sweeper: %w", zone, err) } + listInstance, err := mongodbAPI.ListInstances(&mongodb.ListInstancesRequest{ Region: extractRegion, }) diff --git a/internal/services/mongodb/types.go b/internal/services/mongodb/types.go index da968802fb..8043a33fb4 100644 --- a/internal/services/mongodb/types.go +++ b/internal/services/mongodb/types.go @@ -6,10 +6,12 @@ import ( func flattenPublicNetwork(endpoints []*mongodb.Endpoint) (interface{}, bool) { publicFlat := []map[string]interface{}(nil) + for _, endpoint := range endpoints { if endpoint.Public == nil { continue } + publicFlat = append(publicFlat, map[string]interface{}{ "id": endpoint.ID, "port": endpoint.Port, diff --git a/internal/services/object/bucket.go b/internal/services/object/bucket.go index 7798722240..bfa720f919 100644 --- a/internal/services/object/bucket.go +++ b/internal/services/object/bucket.go @@ -226,6 +226,7 @@ func ResourceBucket() *schema.Resource { func resourceObjectBucketCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { bucketName := d.Get("name").(string) + s3Client, region, err := s3ClientWithRegion(ctx, d, m) if err != nil { return diag.FromErr(err) @@ -248,6 +249,7 @@ func resourceObjectBucketCreate(ctx context.Context, d *schema.ResourceData, m i if TimedOut(err) { _, err = s3Client.CreateBucket(ctx, req) } + if err != nil { return diag.FromErr(err) } @@ -314,6 +316,7 @@ func resourceObjectBucketUpdate(ctx context.Context, d *schema.ResourceData, m i Bucket: scw.StringPtr(bucketName), }) } + if err != nil { return diag.FromErr(err) } @@ -370,10 +373,12 @@ func resourceBucketLifecycleUpdate(ctx context.Context, conn *s3.Client, d *sche lifecycleRuleAndOp := &s3Types.LifecycleRuleAndOperator{ Tags: tags, } + if ruleHasPrefix { prefix := r["prefix"].(string) lifecycleRuleAndOp.Prefix = &prefix } + filter.And = lifecycleRuleAndOp } @@ -413,10 +418,12 @@ func resourceBucketLifecycleUpdate(ctx context.Context, conn *s3.Client, d *sche if len(expiration) > 0 && expiration[0] != nil { e := expiration[0].(map[string]interface{}) i := &s3Types.LifecycleExpiration{} + if val, ok := e["days"].(int); ok && val > 0 { days := int32(val) i.Days = aws.Int32(days) } + rule.Expiration = i } @@ -424,13 +431,16 @@ func resourceBucketLifecycleUpdate(ctx context.Context, conn *s3.Client, d *sche transitions := d.Get(fmt.Sprintf("lifecycle_rule.%d.transition", i)).(*schema.Set).List() if len(transitions) > 0 { rule.Transitions = []s3Types.Transition{} + for _, transition := range transitions { transition := transition.(map[string]interface{}) i := s3Types.Transition{} + if val, ok := transition["days"].(int); ok && val >= 0 { days := int32(val) i.Days = aws.Int32(days) } + if val, ok := transition["storage_class"].(string); ok && val != "" { i.StorageClass = s3Types.TransitionStorageClass(val) } @@ -490,6 +500,7 @@ func resourceObjectBucketRead(ctx context.Context, d *schema.ResourceData, m int return diags } + if !objectLockFound { _ = d.Set("object_lock_enabled", false) } @@ -559,6 +570,7 @@ func resourceObjectBucketRead(ctx context.Context, d *schema.ResourceData, m int return diags } } + _ = d.Set("versioning", flattenObjectBucketVersioning(versioningResponse)) // Read the lifecycle configuration @@ -579,12 +591,14 @@ func resourceObjectBucketRead(ctx context.Context, d *schema.ResourceData, m int for _, lifecycleRule := range lifecycle.Rules { log.Printf("[DEBUG] SCW bucket: %s, read lifecycle rule: %v", d.Id(), lifecycleRule) + rule := make(map[string]interface{}) // ID if lifecycleRule.ID != nil && aws.ToString(lifecycleRule.ID) != "" { rule["id"] = aws.ToString(lifecycleRule.ID) } + filter := lifecycleRule.Filter if filter != nil { if filter.And != nil { @@ -632,27 +646,33 @@ func resourceObjectBucketRead(ctx context.Context, d *schema.ResourceData, m int if lifecycleRule.Expiration.Days != nil { e["days"] = int(aws.ToInt32(lifecycleRule.Expiration.Days)) } + rule["expiration"] = []interface{}{e} } //// transition if len(lifecycleRule.Transitions) > 0 { transitions := make([]interface{}, 0, len(lifecycleRule.Transitions)) + for _, v := range lifecycleRule.Transitions { t := make(map[string]interface{}) if v.Days != nil { t["days"] = int(aws.ToInt32(v.Days)) } + if v.StorageClass != "" { t["storage_class"] = string(v.StorageClass) } + transitions = append(transitions, t) } + rule["transition"] = schema.NewSet(transitionHash, transitions) } lifecycleRules = append(lifecycleRules, rule) } } + if err := d.Set("lifecycle_rule", lifecycleRules); err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, @@ -665,7 +685,9 @@ func resourceObjectBucketRead(ctx context.Context, d *schema.ResourceData, m int func resourceObjectBucketDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { s3Client, _, bucketName, err := s3ClientWithRegionAndName(ctx, d, m, d.Id()) + var nObjectDeleted int64 + if err != nil { return diag.FromErr(err) } @@ -685,6 +707,7 @@ func resourceObjectBucketDelete(ctx context.Context, d *schema.ResourceData, m i if err != nil { return diag.FromErr(fmt.Errorf("error S3 bucket force_destroy: %s", err)) } + log.Printf("[DEBUG] Deleted %d S3 objects", nObjectDeleted) return resourceObjectBucketDelete(ctx, d, m) diff --git a/internal/services/object/bucket_acl.go b/internal/services/object/bucket_acl.go index f31b85727d..04563e1474 100644 --- a/internal/services/object/bucket_acl.go +++ b/internal/services/object/bucket_acl.go @@ -163,6 +163,7 @@ func resourceBucketACLCreate(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } + region = bucketRegion } @@ -189,6 +190,7 @@ func resourceBucketACLCreate(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(fmt.Errorf("error putting Object Storage ACL: %s", err)) } + tflog.Debug(ctx, fmt.Sprintf("output: %v", out)) d.SetId(BucketACLCreateResourceID(region, bucket, acl)) @@ -374,6 +376,7 @@ func flattenBucketACLAccessControlPolicyOwner(owner *s3Types.Owner) []interface{ func resourceBucketACLRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { expectedBucketOwner := d.Get("expected_bucket_owner") + conn, region, bucket, acl, err := s3ClientWithRegionWithNameACL(ctx, d, m, d.Id()) if err != nil { return diag.FromErr(err) @@ -406,9 +409,11 @@ func resourceBucketACLRead(ctx context.Context, d *schema.ResourceData, m interf _ = d.Set("acl", acl) _ = d.Set("expected_bucket_owner", expectedBucketOwner) + if err := d.Set("access_control_policy", flattenBucketACLAccessControlPolicy(output)); err != nil { return diag.FromErr(fmt.Errorf("error setting access_control_policy: %w", err)) } + _ = d.Set("region", region) _ = d.Set("project_id", NormalizeOwnerID(output.Owner.ID)) _ = d.Set("bucket", locality.ExpandID(bucket)) diff --git a/internal/services/object/bucket_acl_test.go b/internal/services/object/bucket_acl_test.go index 8833e92aa3..c1f1b1c047 100644 --- a/internal/services/object/bucket_acl_test.go +++ b/internal/services/object/bucket_acl_test.go @@ -26,6 +26,7 @@ const ( func TestAccObjectBucketACL_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testBucketName := sdkacctest.RandomWithPrefix("tf-tests-scw-object-acl-basic") resource.Test(t, resource.TestCase{ @@ -76,6 +77,7 @@ func TestAccObjectBucketACL_Basic(t *testing.T) { func TestAccObjectBucketACL_Grantee(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testBucketName := sdkacctest.RandomWithPrefix("tf-tests-scw-object-acl-grantee") ownerID := "105bdce1-64c0-48ab-899d-868455867ecf" @@ -179,6 +181,7 @@ func TestAccObjectBucketACL_Grantee(t *testing.T) { func TestAccObjectBucketACL_GranteeWithOwner(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testBucketName := sdkacctest.RandomWithPrefix("tf-tests-scw-object-acl-owner") ownerID := "105bdce1-64c0-48ab-899d-868455867ecf" resource.Test(t, resource.TestCase{ @@ -231,6 +234,7 @@ func TestAccObjectBucketACL_GranteeWithOwner(t *testing.T) { func TestAccObjectBucketACL_WithBucketName(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testBucketName := sdkacctest.RandomWithPrefix("tf-tests-scw-object-acl-name") resource.Test(t, resource.TestCase{ @@ -279,6 +283,7 @@ func TestAccObjectBucketACL_WithBucketName(t *testing.T) { func TestAccObjectBucketACL_Remove(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testBucketName := sdkacctest.RandomWithPrefix("tf-tests-scw-object-acl-remove") resource.Test(t, resource.TestCase{ @@ -355,21 +360,25 @@ func TestAccObjectBucketACL_Remove(t *testing.T) { func testAccObjectBucketACLCheck(tt *acctest.TestTools, name string, expectedACL string) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() + rs, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("resource not found: %s", name) } bucketRegion := rs.Primary.Attributes["region"] + s3Client, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion) if err != nil { return err } + if rs.Primary.ID == "" { return errors.New("no ID is set") } bucketName := rs.Primary.Attributes["name"] + actualACL, err := s3Client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: types.ExpandStringPtr(bucketName), }) @@ -389,10 +398,12 @@ func testAccObjectBucketACLCheck(tt *acctest.TestTools, name string, expectedACL func s3ACLAreEqual(expected string, actual *s3.GetBucketAclOutput) (errs []error) { ownerID := *object.NormalizeOwnerID(actual.Owner.ID) grantsMap := make(map[string]string) + for _, actualACL := range actual.Grants { if actualACL.Permission == "" { return append(errs, errors.New("grant has no permission")) } + if actualACL.Grantee.ID != nil { grantsMap[string(actualACL.Permission)] = *object.NormalizeOwnerID(actualACL.Grantee.ID) } else { @@ -408,6 +419,7 @@ func s3ACLAreEqual(expected string, actual *s3.GetBucketAclOutput) (errs []error return errs } + if grantsMap["FULL_CONTROL"] != ownerID { errs = append(errs, fmt.Errorf("expected FULL_CONTROL to be granted to owner (%s), instead got %q", ownerID, grantsMap["FULL_CONTROL"])) } @@ -418,9 +430,11 @@ func s3ACLAreEqual(expected string, actual *s3.GetBucketAclOutput) (errs []error return errs } + if grantsMap["FULL_CONTROL"] != ownerID { errs = append(errs, fmt.Errorf("expected FULL_CONTROL to be granted to owner (%s), instead got %q", ownerID, grantsMap["FULL_CONTROL"])) } + if grantsMap["READ"] != s3ACLGranteeAllUsers { errs = append(errs, fmt.Errorf("expected READ to be granted to %q, instead got %q", s3ACLGranteeAllUsers, grantsMap["READ"])) } @@ -431,12 +445,15 @@ func s3ACLAreEqual(expected string, actual *s3.GetBucketAclOutput) (errs []error return errs } + if grantsMap["FULL_CONTROL"] != ownerID { errs = append(errs, fmt.Errorf("expected FULL_CONTROL to be granted to owner (%s), instead got %q", ownerID, grantsMap["FULL_CONTROL"])) } + if grantsMap["READ"] != s3ACLGranteeAllUsers { errs = append(errs, fmt.Errorf("expected READ to be granted to %q, instead got %q", s3ACLGranteeAllUsers, grantsMap["READ"])) } + if grantsMap["WRITE"] != s3ACLGranteeAllUsers { errs = append(errs, fmt.Errorf("expected WRITE to be granted to %q, instead got %q", s3ACLGranteeAllUsers, grantsMap["WRITE"])) } @@ -447,9 +464,11 @@ func s3ACLAreEqual(expected string, actual *s3.GetBucketAclOutput) (errs []error return errs } + if grantsMap["FULL_CONTROL"] != ownerID { errs = append(errs, fmt.Errorf("expected FULL_CONTROL to be granted to owner (%s), instead got %q", ownerID, grantsMap["FULL_CONTROL"])) } + if grantsMap["READ"] != s3ACLGranteeAuthenticatedUsers { errs = append(errs, fmt.Errorf("expected READ to be granted to %q, instead got %q", s3ACLGranteeAuthenticatedUsers, grantsMap["READ"])) } diff --git a/internal/services/object/bucket_lock_configuration.go b/internal/services/object/bucket_lock_configuration.go index 277a54eb82..314e5e79fb 100644 --- a/internal/services/object/bucket_lock_configuration.go +++ b/internal/services/object/bucket_lock_configuration.go @@ -96,6 +96,7 @@ func resourceObjectLockConfigurationCreate(ctx context.Context, d *schema.Resour if err != nil { return diag.FromErr(err) } + region = bucketRegion } @@ -152,6 +153,7 @@ func resourceObjectLockConfigurationRead(ctx context.Context, d *schema.Resource if err != nil { return diag.FromErr(fmt.Errorf("couldn't read bucket acl: %s", err)) } + _ = d.Set("project_id", NormalizeOwnerID(acl.Owner.ID)) _ = d.Set("bucket", bucket) diff --git a/internal/services/object/bucket_lock_configuration_test.go b/internal/services/object/bucket_lock_configuration_test.go index 9169ba3e54..77e6418962 100644 --- a/internal/services/object/bucket_lock_configuration_test.go +++ b/internal/services/object/bucket_lock_configuration_test.go @@ -302,6 +302,7 @@ func TestAccObjectBucketLockConfiguration_WithBucketName(t *testing.T) { func testAccCheckBucketLockConfigurationDestroy(tt *acctest.TestTools) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() + for _, rs := range s.RootModule().Resources { if rs.Type != "scaleway_object_bucket_lock_configuration" { continue @@ -310,6 +311,7 @@ func testAccCheckBucketLockConfigurationDestroy(tt *acctest.TestTools) resource. regionalID := regional.ExpandID(rs.Primary.ID) bucketRegion := regionalID.Region bucket := regionalID.ID + conn, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion.String()) if err != nil { return err @@ -341,6 +343,7 @@ func testAccCheckBucketLockConfigurationDestroy(tt *acctest.TestTools) resource. func testAccCheckBucketLockConfigurationExists(tt *acctest.TestTools, resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() + rs := s.RootModule().Resources[resourceName] if rs == nil { return errors.New("resource not found") @@ -358,6 +361,7 @@ func testAccCheckBucketLockConfigurationExists(tt *acctest.TestTools, resourceNa regionalID := regional.ExpandID(rs.Primary.ID) bucketRegion := regionalID.Region bucket := regionalID.ID + conn, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion.String()) if err != nil { return err diff --git a/internal/services/object/bucket_policy.go b/internal/services/object/bucket_policy.go index 57c300fc9a..d8678b2b3e 100644 --- a/internal/services/object/bucket_policy.go +++ b/internal/services/object/bucket_policy.go @@ -59,6 +59,7 @@ func resourceObjectBucketPolicyCreate(ctx context.Context, d *schema.ResourceDat regionalID := regional.ExpandID(d.Get("bucket")) bucket := regionalID.ID bucketRegion := regionalID.Region + tflog.Debug(ctx, "bucket name: "+bucket) if bucketRegion != "" && bucketRegion != region { @@ -66,6 +67,7 @@ func resourceObjectBucketPolicyCreate(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(err) } + region = bucketRegion } @@ -86,6 +88,7 @@ func resourceObjectBucketPolicyCreate(ctx context.Context, d *schema.ResourceDat if tfawserr.ErrCodeEquals(err, "MalformedPolicy") { return retry.RetryableError(err) } + if err != nil { return retry.NonRetryableError(err) } @@ -153,6 +156,7 @@ func resourceObjectBucketPolicyRead(ctx context.Context, d *schema.ResourceData, } var diags diag.Diagnostics + acl, err := s3Client.GetBucketAcl(ctx, &s3.GetBucketAclInput{ Bucket: aws.String(bucket), }) diff --git a/internal/services/object/bucket_policy_test.go b/internal/services/object/bucket_policy_test.go index 8125995861..007c7b241a 100644 --- a/internal/services/object/bucket_policy_test.go +++ b/internal/services/object/bucket_policy_test.go @@ -249,12 +249,14 @@ func TestAccObjectBucketPolicy_OtherRegionWithBucketName(t *testing.T) { func testAccCheckBucketHasPolicy(tt *acctest.TestTools, n string, expectedPolicyText string) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() + rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("not found: %s", n) } bucketRegion := rs.Primary.Attributes["region"] + s3Client, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion) if err != nil { return err @@ -265,6 +267,7 @@ func testAccCheckBucketHasPolicy(tt *acctest.TestTools, n string, expectedPolicy } bucketName := rs.Primary.Attributes["name"] + policy, err := s3Client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{ Bucket: types.ExpandStringPtr(bucketName), }) @@ -273,6 +276,7 @@ func testAccCheckBucketHasPolicy(tt *acctest.TestTools, n string, expectedPolicy } actualPolicyText := *policy.Policy + actualPolicyText, err = removePolicyStatementResources(actualPolicyText) if err != nil { return err @@ -282,6 +286,7 @@ func testAccCheckBucketHasPolicy(tt *acctest.TestTools, n string, expectedPolicy if err != nil { return fmt.Errorf("error testing policy equivalence: %s", err) } + if !equivalent { return fmt.Errorf("non equivalent policy error:\n\nexpected: %s\n\n got: %s", expectedPolicyText, actualPolicyText) @@ -296,6 +301,7 @@ func testAccCheckBucketHasPolicy(tt *acctest.TestTools, n string, expectedPolicy // policy["Statement"][i]["Resource"] func removePolicyStatementResources(policy string) (string, error) { actualPolicyJSON := make(map[string]interface{}) + err := json.Unmarshal([]byte(policy), &actualPolicyJSON) if err != nil { return "", fmt.Errorf("json.Unmarshal error: %v", err) diff --git a/internal/services/object/bucket_test.go b/internal/services/object/bucket_test.go index 0df21d630a..ebe0f8ee62 100644 --- a/internal/services/object/bucket_test.go +++ b/internal/services/object/bucket_test.go @@ -26,6 +26,7 @@ import ( func TestAccObjectBucket_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testBucketACL := "private" testBucketUpdatedACL := "public-read" bucketBasic := sdkacctest.RandomWithPrefix("tf-tests-scaleway-object-bucket-basic") @@ -122,6 +123,7 @@ func TestAccObjectBucket_Basic(t *testing.T) { func TestAccObjectBucket_Lifecycle(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketLifecycle := sdkacctest.RandomWithPrefix("tf-tests-scaleway-object-bucket-lifecycle") resourceNameLifecycle := "scaleway_object_bucket.main-bucket-lifecycle" resource.ParallelTest(t, resource.TestCase{ @@ -408,6 +410,7 @@ func TestAccObjectBucket_Lifecycle(t *testing.T) { func TestAccObjectBucket_ObjectLock(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketObjectLock := sdkacctest.RandomWithPrefix("tf-tests-scaleway-object-bucket-lock") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -574,6 +577,7 @@ func TestAccObjectBucket_Cors_Update(t *testing.T) { func TestAccObjectBucket_Cors_Delete(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + ctx := context.Background() resourceName := "scaleway_object_bucket.bucket" @@ -584,11 +588,14 @@ func TestAccObjectBucket_Cors_Delete(t *testing.T) { if !ok { return fmt.Errorf("not found: %s", n) } + bucketRegion := rs.Primary.Attributes["region"] + conn, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion) if err != nil { return err } + _, err = conn.DeleteBucketCors(ctx, &s3.DeleteBucketCorsInput{ Bucket: scw.StringPtr(rs.Primary.Attributes["name"]), }) @@ -664,6 +671,7 @@ func testAccCheckObjectBucketCors(tt *acctest.TestTools, n string, corsRules []s rs := s.RootModule().Resources[n] bucketName := rs.Primary.Attributes["name"] bucketRegion := rs.Primary.Attributes["region"] + s3Client, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion) if err != nil { return err @@ -707,11 +715,14 @@ func TestAccObjectBucket_DestroyForce(t *testing.T) { addObjectToBucket := func(tt *acctest.TestTools, n string) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() + rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("not found: %s", n) } + bucketRegion := rs.Primary.Attributes["region"] + conn, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion) if err != nil { return err @@ -780,6 +791,7 @@ func TestAccObjectBucket_DestroyForce(t *testing.T) { func testAccCheckObjectBucketLifecycleConfigurationExists(tt *acctest.TestTools, n string) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() + rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("not found: %s", n) @@ -790,6 +802,7 @@ func testAccCheckObjectBucketLifecycleConfigurationExists(tt *acctest.TestTools, } bucketRegion := rs.Primary.Attributes["region"] + s3Client, err := object.NewS3ClientFromMeta(ctx, tt.Meta, bucketRegion) if err != nil { return err diff --git a/internal/services/object/bucket_website_configuration.go b/internal/services/object/bucket_website_configuration.go index 2c0735ead4..3c9cd4841d 100644 --- a/internal/services/object/bucket_website_configuration.go +++ b/internal/services/object/bucket_website_configuration.go @@ -96,6 +96,7 @@ func resourceBucketWebsiteConfigurationCreate(ctx context.Context, d *schema.Res if err != nil { return diag.FromErr(err) } + region = bucketRegion } @@ -167,6 +168,7 @@ func resourceBucketWebsiteConfigurationRead(ctx context.Context, d *schema.Resou if d.IsNewResource() { return diag.FromErr(fmt.Errorf("error reading object bucket website configuration (%s): empty output", d.Id())) } + tflog.Info(ctx, fmt.Sprintf("[WARN] object Bucket Website Configuration (%s) not found, removing from state", d.Id())) d.SetId("") @@ -193,6 +195,7 @@ func resourceBucketWebsiteConfigurationRead(ctx context.Context, d *schema.Resou if err != nil { return diag.FromErr(fmt.Errorf("couldn't read bucket acl: %s", err)) } + _ = d.Set("project_id", NormalizeOwnerID(acl.Owner.ID)) return nil diff --git a/internal/services/object/clients.go b/internal/services/object/clients.go index a0e687edd8..aea1bb49fe 100644 --- a/internal/services/object/clients.go +++ b/internal/services/object/clients.go @@ -11,6 +11,7 @@ import ( // SharedS3ClientForRegion returns a common S3 client needed for the sweeper func SharedS3ClientForRegion(region scw.Region) (*s3.Client, error) { ctx := context.Background() + m, err := meta.NewMeta(ctx, &meta.Config{ TerraformVersion: "terraform-tests", ForceZone: region.GetZones()[0], diff --git a/internal/services/object/data_source_object_bucket.go b/internal/services/object/data_source_object_bucket.go index 91a7a840cb..fe809a1cb9 100644 --- a/internal/services/object/data_source_object_bucket.go +++ b/internal/services/object/data_source_object_bucket.go @@ -41,6 +41,7 @@ func DataSourceObjectStorageRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + region = bucketRegion } @@ -49,6 +50,7 @@ func DataSourceObjectStorageRead(ctx context.Context, d *schema.ResourceData, m } log.Printf("[DEBUG] Reading Object Storage bucket: %s", bucket) + _, err = s3Client.HeadBucket(ctx, input) if err != nil { return diag.FromErr(fmt.Errorf("failed getting Object Storage bucket (%s): %w", bucket, err)) @@ -60,6 +62,7 @@ func DataSourceObjectStorageRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(fmt.Errorf("couldn't read bucket acl: %s", err)) } + _ = d.Set("project_id", NormalizeOwnerID(acl.Owner.ID)) bucketRegionalID := regional.NewIDString(region, bucket) diff --git a/internal/services/object/data_source_object_bucket_policy.go b/internal/services/object/data_source_object_bucket_policy.go index 4272c46f90..668dabcf9f 100644 --- a/internal/services/object/data_source_object_bucket_policy.go +++ b/internal/services/object/data_source_object_bucket_policy.go @@ -37,6 +37,7 @@ func DataSourceObjectBucketPolicyRead(ctx context.Context, d *schema.ResourceDat regionalID := regional.ExpandID(d.Get("bucket")) bucket := regionalID.ID bucketRegion := regionalID.Region + tflog.Debug(ctx, "bucket name: "+bucket) if bucketRegion != "" && bucketRegion != region { @@ -44,11 +45,14 @@ func DataSourceObjectBucketPolicyRead(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(err) } + region = bucketRegion } + _ = d.Set("region", region) tflog.Debug(ctx, "[DEBUG] SCW bucket policy, read for bucket: "+d.Id()) + policy, err := s3Client.GetBucketPolicy(ctx, &s3.GetBucketPolicyInput{ Bucket: aws.String(bucket), }) @@ -78,6 +82,7 @@ func DataSourceObjectBucketPolicyRead(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(fmt.Errorf("couldn't read bucket acl: %s", err)) } + _ = d.Set("project_id", NormalizeOwnerID(acl.Owner.ID)) d.SetId(regional.NewIDString(region, bucket)) diff --git a/internal/services/object/data_source_object_bucket_policy_test.go b/internal/services/object/data_source_object_bucket_policy_test.go index f0a129f364..f01cf23c6a 100644 --- a/internal/services/object/data_source_object_bucket_policy_test.go +++ b/internal/services/object/data_source_object_bucket_policy_test.go @@ -98,6 +98,7 @@ func testAccCheckDataSourcePolicyIsEquivalent(n, expectedPolicyText string) reso if !ok { return fmt.Errorf("not found: %s", n) } + dataSourcePolicy := ds.Primary.Attributes["policy"] dataSourcePolicyToCompare, err := removePolicyStatementResources(dataSourcePolicy) @@ -109,6 +110,7 @@ func testAccCheckDataSourcePolicyIsEquivalent(n, expectedPolicyText string) reso if err != nil { return fmt.Errorf("error testing policy equivalence: %s", err) } + if !equivalent { return fmt.Errorf("non equivalent policy error:\n\nexpected: %s\n\n got: %s", expectedPolicyText, dataSourcePolicyToCompare) diff --git a/internal/services/object/data_source_object_bucket_test.go b/internal/services/object/data_source_object_bucket_test.go index 5b261a2903..53a5952220 100644 --- a/internal/services/object/data_source_object_bucket_test.go +++ b/internal/services/object/data_source_object_bucket_test.go @@ -17,6 +17,7 @@ import ( func TestAccDataSourceObjectBucket_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("tf-tests-scaleway-object-bucket") objectBucketTestDefaultRegion, _ := tt.Meta.ScwClient().GetDefaultRegion() @@ -90,6 +91,7 @@ func TestAccDataSourceObjectBucket_Basic(t *testing.T) { func TestAccDataSourceObjectBucket_ProjectIDAllowed(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("tf-tests-scaleway-object-bucket") project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeSideProject(tt) @@ -139,6 +141,7 @@ func TestAccDataSourceObjectBucket_ProjectIDAllowed(t *testing.T) { func TestAccDataSourceObjectBucket_ProjectIDForbidden(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-bucket") project, iamAPIKey, terminateFakeSideProject, err := acctest.CreateFakeSideProject(tt) diff --git a/internal/services/object/helpers_object.go b/internal/services/object/helpers_object.go index c6b4670c46..2babd8b292 100644 --- a/internal/services/object/helpers_object.go +++ b/internal/services/object/helpers_object.go @@ -49,6 +49,7 @@ func (r *scalewayResolver) ResolveEndpoint(ctx context.Context, params s3.Endpoi func newS3Client(ctx context.Context, region, accessKey, secretKey string, httpClient *http.Client) (*s3.Client, error) { endpoint := "https://s3." + region + ".scw.cloud" + cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), @@ -57,6 +58,7 @@ func newS3Client(ctx context.Context, region, accessKey, secretKey string, httpC if err != nil { return nil, err } + client := s3.NewFromConfig(cfg, func(o *s3.Options) { o.BaseEndpoint = aws.String(endpoint) o.EndpointResolverV2 = &scalewayResolver{region: region} @@ -92,6 +94,7 @@ func s3ClientWithRegion(ctx context.Context, d *schema.ResourceData, m interface if projectID, _, err := meta.ExtractProjectID(d, m); err == nil { accessKey = accessKeyWithProjectID(accessKey, projectID) } + secretKey, _ := meta.ExtractScwClient(m).GetSecretKey() s3Client, err := newS3Client(ctx, region.String(), accessKey, secretKey, meta.ExtractHTTPClient(m)) @@ -112,6 +115,7 @@ func s3ClientWithRegionAndName(ctx context.Context, d *schema.ResourceData, m in if len(parts) > 2 { return nil, "", "", fmt.Errorf("invalid ID %q: expected ID in format /[@]", id) } + name = parts[0] d.SetId(fmt.Sprintf("%s/%s", region, name)) @@ -146,6 +150,7 @@ func s3ClientWithRegionAndNestedName(ctx context.Context, d *schema.ResourceData if projectID, _, err := meta.ExtractProjectID(d, m); err == nil { accessKey = accessKeyWithProjectID(accessKey, projectID) } + secretKey, _ := meta.ExtractScwClient(m).GetSecretKey() s3Client, err := newS3Client(ctx, region.String(), accessKey, secretKey, meta.ExtractHTTPClient(m)) @@ -166,6 +171,7 @@ func s3ClientWithRegionWithNameACL(ctx context.Context, d *schema.ResourceData, if projectID, _, err := meta.ExtractProjectID(d, m); err == nil { accessKey = accessKeyWithProjectID(accessKey, projectID) } + secretKey, _ := meta.ExtractScwClient(m).GetSecretKey() s3Client, err := newS3Client(ctx, region, accessKey, secretKey, meta.ExtractHTTPClient(m)) @@ -181,6 +187,7 @@ func s3ClientForceRegion(ctx context.Context, d *schema.ResourceData, m interfac if projectID, _, err := meta.ExtractProjectID(d, m); err == nil { accessKey = accessKeyWithProjectID(accessKey, projectID) } + secretKey, _ := meta.ExtractScwClient(m).GetSecretKey() s3Client, err := newS3Client(ctx, region, accessKey, secretKey, meta.ExtractHTTPClient(m)) @@ -200,13 +207,17 @@ func flattenObjectBucketTags(tagsSet []s3Types.Tag) map[string]interface{} { for _, tagSet := range tagsSet { var key string + var value string + if tagSet.Key != nil { key = *tagSet.Key } + if tagSet.Value != nil { value = *tagSet.Value } + tags[key] = value } @@ -256,6 +267,7 @@ func flattenObjectBucketVersioning(versioningResponse *s3.GetBucketVersioningOut func expandObjectBucketVersioning(v []interface{}) *s3Types.VersioningConfiguration { vc := &s3Types.VersioningConfiguration{} vc.Status = s3Types.BucketVersioningStatusSuspended + if len(v) > 0 { if c := v[0].(map[string]interface{}); c["enabled"].(bool) { vc.Status = s3Types.BucketVersioningStatusEnabled @@ -269,25 +281,32 @@ func flattenBucketCORS(corsResponse interface{}) []interface{} { if corsResponse == nil { return nil } + if cors, ok := corsResponse.(*s3.GetBucketCorsOutput); ok && cors != nil && len(cors.CORSRules) > 0 { var corsRules []interface{} + for _, ruleObject := range cors.CORSRules { rule := map[string]interface{}{} if len(ruleObject.AllowedHeaders) > 0 { rule["allowed_headers"] = ruleObject.AllowedHeaders } + if len(ruleObject.AllowedMethods) > 0 { rule["allowed_methods"] = ruleObject.AllowedMethods } + if len(ruleObject.AllowedOrigins) > 0 { rule["allowed_origins"] = ruleObject.AllowedOrigins } + if len(ruleObject.ExposeHeaders) > 0 { rule["expose_headers"] = ruleObject.ExposeHeaders } + if ruleObject.MaxAgeSeconds != nil { rule["max_age_seconds"] = ruleObject.MaxAgeSeconds } + corsRules = append(corsRules, rule) } @@ -316,8 +335,10 @@ func expandBucketCORS(ctx context.Context, rawCors []interface{}, bucket string) } rule := s3Types.CORSRule{} + for key, value := range corsMap { tflog.Debug(ctx, fmt.Sprintf("Processing CORS key: %s, value: %#v for bucket %s", key, value, bucket)) + switch key { case "allowed_headers": rule.AllowedHeaders = toStringSlice(ctx, value) @@ -346,6 +367,7 @@ func expandBucketCORS(ctx context.Context, rawCors []interface{}, bucket string) func toStringSlice(ctx context.Context, input interface{}) []string { var result []string + switch v := input.(type) { case []interface{}: for _, item := range v { @@ -389,9 +411,11 @@ func removeS3ObjectVersionLegalHold(ctx context.Context, conn *s3.Client, bucket return false, err } + if objectHead.ObjectLockLegalHoldStatus != s3Types.ObjectLockLegalHoldStatusOn { return false, nil } + _, err = conn.PutObjectLegalHold(ctx, &s3.PutObjectLegalHoldInput{ Bucket: scw.StringPtr(bucketName), Key: objectVersion.Key, @@ -411,7 +435,9 @@ func removeS3ObjectVersionLegalHold(ctx context.Context, conn *s3.Client, bucket func emptyBucket(ctx context.Context, conn *s3.Client, bucketName string, force bool) (int64, error) { var nObject int64 + var nObjectMarker int64 + var globalErr error // Delete All Object Version @@ -433,6 +459,7 @@ func emptyBucket(ctx context.Context, conn *s3.Client, bucketName string, force func processAllPagesObject(ctx context.Context, bucketName string, conn *s3.Client, force bool, fn func(ctx context.Context, conn *s3.Client, bucket string, force bool, page *s3.ListObjectVersionsOutput, pool *workerpool.WorkerPool) (int64, error)) (int64, error) { var globalErr error + deletionWorkers := findDeletionWorkerCapacity() nObject := int64(0) input := &s3.ListObjectVersionsInput{ @@ -446,10 +473,12 @@ func processAllPagesObject(ctx context.Context, bucketName string, conn *s3.Clie if err != nil { return nObject, fmt.Errorf("error listing S3 objects: %w", err) } + n, taskErr := fn(ctx, conn, bucketName, force, page, pool) if taskErr != nil { return nObject, taskErr } + nObject += n } @@ -464,14 +493,17 @@ func processAllPagesObject(ctx context.Context, bucketName string, conn *s3.Clie func deleteMarkerBucket(ctx context.Context, conn *s3.Client, bucketName string, force bool, page *s3.ListObjectVersionsOutput, pool *workerpool.WorkerPool) (int64, error) { var nObject int64 + for _, deleteMarkerEntry := range page.DeleteMarkers { pool.AddTask(func() error { deleteMarkerKey := aws.ToString(deleteMarkerEntry.Key) deleteMarkerVersionsID := aws.ToString(deleteMarkerEntry.VersionId) + err := deleteS3ObjectVersion(ctx, conn, bucketName, deleteMarkerKey, deleteMarkerVersionsID, force) if err != nil { return fmt.Errorf("failed to delete S3 object delete marker: %s", err) } + nObject++ return nil @@ -483,6 +515,7 @@ func deleteMarkerBucket(ctx context.Context, conn *s3.Client, bucketName string, func deleteVersionBucket(ctx context.Context, conn *s3.Client, bucketName string, force bool, page *s3.ListObjectVersionsOutput, pool *workerpool.WorkerPool) (int64, error) { var nObject int64 + for _, objectVersion := range page.Versions { pool.AddTask(func() error { objectKey := aws.ToString(objectVersion.Key) @@ -499,7 +532,9 @@ func deleteVersionBucket(ctx context.Context, conn *s3.Client, bucketName string err = deleteS3ObjectVersion(ctx, conn, bucketName, objectKey, objectVersionID, force) } } + nObject++ + if err != nil { return fmt.Errorf("failed to delete S3 object: %s", err) } @@ -522,6 +557,7 @@ func findDeletionWorkerCapacity() int { func transitionHash(v interface{}) int { var buf bytes.Buffer + m, ok := v.(map[string]interface{}) if !ok { @@ -531,6 +567,7 @@ func transitionHash(v interface{}) int { if v, ok := m["days"]; ok { buf.WriteString(fmt.Sprintf("%d-", v.(int))) } + if v, ok := m["storage_class"]; ok { buf.WriteString(v.(string) + "-") } @@ -561,6 +598,7 @@ func TransitionSCWStorageClassValues() []string { func SuppressEquivalentPolicyDiffs(k, old, newP string, _ *schema.ResourceData) bool { tflog.Debug(context.Background(), fmt.Sprintf("[DEBUG] suppress policy on key: %s, old: %s new: %s", k, old, newP)) + if strings.TrimSpace(old) == "" && strings.TrimSpace(newP) == "" { return true } diff --git a/internal/services/object/object.go b/internal/services/object/object.go index 753aebe852..c1e7893952 100644 --- a/internal/services/object/object.go +++ b/internal/services/object/object.go @@ -138,6 +138,7 @@ func resourceObjectCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + region = bucketRegion } @@ -162,6 +163,7 @@ func resourceObjectCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + req.SSECustomerAlgorithm = scw.StringPtr("AES256") req.SSECustomerKeyMD5 = &digestMD5 req.SSECustomerKey = encryption @@ -172,6 +174,7 @@ func resourceObjectCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + req.Body = file } else if content, hasContent := d.GetOk("content"); hasContent { contentString := []byte(content.(string)) @@ -179,10 +182,12 @@ func resourceObjectCreate(ctx context.Context, d *schema.ResourceData, m interfa } else if content, hasContent := d.GetOk("content_base64"); hasContent { contentString := []byte(content.(string)) decoded := make([]byte, base64.StdEncoding.DecodedLen(len(contentString))) + _, err = base64.StdEncoding.Decode(decoded, contentString) if err != nil { return diag.FromErr(err) } + req.Body = bytes.NewReader(decoded) } else { req.Body = bytes.NewReader([]byte{}) @@ -214,10 +219,12 @@ func resourceObjectCreate(ctx context.Context, d *schema.ResourceData, m interfa func EncryptCustomerKey(encryptionKeyStr string) (string, *string, error) { encryptionKey := []byte(encryptionKeyStr) h := md5.New() //nolint:gosec + _, err := h.Write(encryptionKey) if err != nil { return "", nil, err } + digest := h.Sum(nil) digestMD5 := base64.StdEncoding.EncodeToString(digest) encryption := aws.String(base64.StdEncoding.EncodeToString(encryptionKey)) @@ -245,24 +252,29 @@ func resourceObjectUpdate(ctx context.Context, d *schema.ResourceData, m interfa Metadata: types.ExpandMapStringString(d.Get("metadata")), ACL: s3Types.ObjectCannedACL(d.Get("visibility").(string)), } + if encryptionKey, ok := d.GetOk("sse_customer_key"); ok { digestMD5, encryption, err := EncryptCustomerKey(encryptionKey.(string)) if err != nil { return diag.FromErr(err) } + req.SSECustomerAlgorithm = scw.StringPtr("AES256") req.SSECustomerKeyMD5 = &digestMD5 req.SSECustomerKey = encryption } + if filePath, hasFile := d.GetOk("file"); hasFile { file, err := os.Open(filePath.(string)) if err != nil { return diag.FromErr(err) } + req.Body = file } else { req.Body = bytes.NewReader([]byte{}) } + _, err = s3Client.PutObject(ctx, req) } else { req := &s3.CopyObjectInput{ @@ -273,17 +285,21 @@ func resourceObjectUpdate(ctx context.Context, d *schema.ResourceData, m interfa Metadata: types.ExpandMapStringString(d.Get("metadata")), ACL: s3Types.ObjectCannedACL(d.Get("visibility").(string)), } + if encryptionKey, ok := d.GetOk("sse_customer_key"); ok { digestMD5, encryption, err := EncryptCustomerKey(encryptionKey.(string)) if err != nil { return diag.FromErr(err) } + req.CopySourceSSECustomerAlgorithm = scw.StringPtr("AES256") req.CopySourceSSECustomerKeyMD5 = &digestMD5 req.CopySourceSSECustomerKey = encryption } + _, err = s3Client.CopyObject(ctx, req) } + if err != nil { return diag.FromErr(err) } @@ -350,6 +366,7 @@ func resourceObjectRead(ctx context.Context, d *schema.ResourceData, m interface delete(obj.Metadata, k) } } + _ = d.Set("metadata", types.FlattenMap(obj.Metadata)) tags, err := s3Client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{ diff --git a/internal/services/object/object_test.go b/internal/services/object/object_test.go index 96df7314a9..b3e47239ff 100644 --- a/internal/services/object/object_test.go +++ b/internal/services/object/object_test.go @@ -30,6 +30,7 @@ const ( func TestAccObject_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-basic") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -109,6 +110,7 @@ func TestAccObject_Basic(t *testing.T) { func TestAccObject_Hash(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-hash") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -169,6 +171,7 @@ func TestAccObject_Hash(t *testing.T) { func TestAccObject_Move(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-move") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -225,6 +228,7 @@ func TestAccObject_Move(t *testing.T) { func TestAccObject_StorageClass(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-storage-class") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -289,6 +293,7 @@ func TestAccObject_StorageClass(t *testing.T) { func TestAccObject_Metadata(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-metadata") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -359,6 +364,7 @@ func TestAccObject_Metadata(t *testing.T) { func TestAccObject_Tags(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-tags") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -423,6 +429,7 @@ func TestAccObject_Tags(t *testing.T) { func TestAccObject_Visibility(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-visibility") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -481,6 +488,7 @@ func TestAccObject_Visibility(t *testing.T) { func TestAccObject_State(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-visibility") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -550,6 +558,7 @@ func TestAccObject_State(t *testing.T) { func TestAccObject_ByContent(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-by-content") fileContentStep1 := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." @@ -608,6 +617,7 @@ func TestAccObject_ByContent(t *testing.T) { func TestAccObject_ByContentBase64(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-by-content-base64") fileContentStep1 := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." @@ -683,6 +693,7 @@ func TestAccObject_ByContentBase64(t *testing.T) { func TestAccObject_WithBucketName(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-basic") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -739,6 +750,7 @@ func TestAccObject_WithBucketName(t *testing.T) { func TestAccObject_Encryption(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + bucketName := sdkacctest.RandomWithPrefix("test-acc-scaleway-object-encryption") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -799,10 +811,12 @@ func TestAccObject_Encryption(t *testing.T) { func testAccCheckObjectExists(tt *acctest.TestTools, n string) resource.TestCheckFunc { return func(state *terraform.State) error { ctx := context.Background() + rs := state.RootModule().Resources[n] if rs == nil { return errors.New("resource not found") } + key := rs.Primary.Attributes["key"] regionalID := regional.ExpandID(rs.Primary.Attributes["bucket"]) diff --git a/internal/services/object/testfuncs/checks.go b/internal/services/object/testfuncs/checks.go index db4b38eb2e..340115aec1 100644 --- a/internal/services/object/testfuncs/checks.go +++ b/internal/services/object/testfuncs/checks.go @@ -19,10 +19,12 @@ import ( func CheckBucketExists(tt *acctest.TestTools, n string, shouldBeAllowed bool) resource.TestCheckFunc { return func(state *terraform.State) error { ctx := context.Background() + rs := state.RootModule().Resources[n] if rs == nil { return errors.New("resource not found") } + bucketName := rs.Primary.Attributes["name"] bucketRegion := rs.Primary.Attributes["region"] @@ -42,6 +44,7 @@ func CheckBucketExists(tt *acctest.TestTools, n string, shouldBeAllowed bool) re if !shouldBeAllowed && object.IsS3Err(err, object.ErrCodeForbidden, object.ErrCodeForbidden) { return nil } + if errors.As(err, new(*types.NoSuchBucket)) { return errors.New("s3 bucket not found") } @@ -56,6 +59,7 @@ func CheckBucketExists(tt *acctest.TestTools, n string, shouldBeAllowed bool) re func IsBucketDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { return func(state *terraform.State) error { ctx := context.Background() + for _, rs := range state.RootModule().Resources { if rs.Type != "scaleway" { continue @@ -92,6 +96,7 @@ func IsBucketDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { func IsObjectDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { return func(state *terraform.State) error { ctx := context.Background() + for _, rs := range state.RootModule().Resources { if rs.Type != "scaleway" { continue @@ -129,6 +134,7 @@ func IsObjectDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { func IsWebsiteConfigurationDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() + for _, rs := range s.RootModule().Resources { if rs.Type != "scaleway_object_bucket_website_configuration" { continue diff --git a/internal/services/object/testfuncs/sweep.go b/internal/services/object/testfuncs/sweep.go index d8cdbd581b..11b83af060 100644 --- a/internal/services/object/testfuncs/sweep.go +++ b/internal/services/object/testfuncs/sweep.go @@ -23,6 +23,7 @@ func testSweepStorageObjectBucket(_ string) error { return acctest.SweepRegions([]scw.Region{scw.RegionFrPar, scw.RegionNlAms, scw.RegionPlWaw}, func(_ *scw.Client, region scw.Region) error { s3client, err := object.SharedS3ClientForRegion(region) ctx := context.Background() + if err != nil { return fmt.Errorf("error getting client: %s", err) } @@ -34,6 +35,7 @@ func testSweepStorageObjectBucket(_ string) error { for _, bucket := range listBucketResponse.Buckets { logging.L.Debugf("Deleting %q bucket", *bucket.Name) + if acctest.IsTestResource(*bucket.Name) { _, err := s3client.DeleteBucket(ctx, &s3.DeleteBucketInput{ Bucket: bucket.Name, diff --git a/internal/services/rdb/acl.go b/internal/services/rdb/acl.go index 2f000ce40e..9de1cf84e7 100644 --- a/internal/services/rdb/acl.go +++ b/internal/services/rdb/acl.go @@ -81,6 +81,7 @@ func ResourceACLCreate(ctx context.Context, d *schema.ResourceData, m interface{ } instanceID := d.Get("instance_id").(string) + _, err = waitForRDBInstance(ctx, api, region, locality.ExpandID(instanceID), d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -90,6 +91,7 @@ func ResourceACLCreate(ctx context.Context, d *schema.ResourceData, m interface{ if err != nil { return diag.FromErr(err) } + createReq := &rdb.SetInstanceACLRulesRequest{ Region: region, InstanceID: locality.ExpandID(instanceID), @@ -149,10 +151,12 @@ func ResourceRdbACLRead(ctx context.Context, d *schema.ResourceData, m interface }) } } + _ = d.Set("acl_rules", aclRules) } else { _ = d.Set("acl_rules", rdbACLRulesFlatten(res.Rules)) } + _ = d.Set("region", region) return diags @@ -179,6 +183,7 @@ func ResourceACLUpdate(ctx context.Context, d *schema.ResourceData, m interface{ if err != nil { return diag.FromErr(err) } + req := &rdb.SetInstanceACLRulesRequest{ Region: region, InstanceID: instanceID, @@ -199,11 +204,14 @@ func ResourceACLDelete(ctx context.Context, d *schema.ResourceData, m interface{ if err != nil { return diag.FromErr(err) } + aclRuleIPs := make([]string, 0) + aclRules, err := rdbACLExpand(d.Get("acl_rules").([]interface{})) if err != nil { return diag.FromErr(err) } + for _, acl := range aclRules { aclRuleIPs = append(aclRuleIPs, acl.IP.String()) } @@ -232,23 +240,28 @@ func ResourceACLDelete(ctx context.Context, d *schema.ResourceData, m interface{ func rdbACLExpand(data []interface{}) ([]*rdb.ACLRuleRequest, error) { var res []*rdb.ACLRuleRequest + for _, rule := range data { r := rule.(map[string]interface{}) ipRaw, ok := r["ip"] if ok { aclRule := &rdb.ACLRuleRequest{} + ip, err := types.ExpandIPNet(ipRaw.(string)) if err != nil { return res, err } + aclRule.IP = ip if descriptionRaw, descriptionExist := r["description"]; descriptionExist { aclRule.Description = descriptionRaw.(string) } + res = append(res, aclRule) } } + sort.Slice(res, func(i, j int) bool { return bytes.Compare(res[i].IP.IP, res[j].IP.IP) < 0 }) @@ -258,15 +271,19 @@ func rdbACLExpand(data []interface{}) ([]*rdb.ACLRuleRequest, error) { func rdbACLRulesFlattenFromSchema(rules []*rdb.ACLRule, dataFromSchema []interface{}) ([]map[string]interface{}, []error) { res := make([]map[string]interface{}, 0, len(dataFromSchema)) + var errors []error + ruleMap := make(map[string]*rdb.ACLRule) for _, rule := range rules { ruleMap[rule.IP.String()] = rule } ruleMapFromSchema := map[string]struct{}{} + for _, ruleFromSchema := range dataFromSchema { currentRule := ruleFromSchema.(map[string]interface{}) + ip, err := types.ExpandIPNet(currentRule["ip"].(string)) if err != nil { errors = append(errors, err) @@ -280,6 +297,7 @@ func rdbACLRulesFlattenFromSchema(rules []*rdb.ACLRule, dataFromSchema []interfa continue } + ruleMapFromSchema[ip.String()] = struct{}{} r := map[string]interface{}{ "ip": aclRule.IP.String(), @@ -311,6 +329,7 @@ func mergeDiffToSchema(rulesFromSchema map[string]struct{}, ruleMap map[string]* func rdbACLRulesFlatten(rules []*rdb.ACLRule) []map[string]interface{} { res := make([]map[string]interface{}, 0, len(rules)) + for _, rule := range rules { r := map[string]interface{}{ "ip": rule.IP.String(), diff --git a/internal/services/rdb/acl_data_source.go b/internal/services/rdb/acl_data_source.go index e6385b8914..33e41453da 100644 --- a/internal/services/rdb/acl_data_source.go +++ b/internal/services/rdb/acl_data_source.go @@ -30,15 +30,18 @@ func DataSourceRDBACLRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + instanceID, _ := d.GetOk("instance_id") _, _, err = locality.ParseLocalizedID(instanceID.(string)) regionalID := instanceID + if err != nil { regionalID = datasource.NewRegionalID(instanceID, region) } d.SetId(regionalID.(string)) + err = d.Set("instance_id", regionalID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/rdb/data_source_rdb_database_backup.go b/internal/services/rdb/data_source_rdb_database_backup.go index 56d7fdb498..1d112d0cb4 100644 --- a/internal/services/rdb/data_source_rdb_database_backup.go +++ b/internal/services/rdb/data_source_rdb_database_backup.go @@ -48,6 +48,7 @@ func DataSourceRDBDatabaseBackupRead(ctx context.Context, d *schema.ResourceData backupID, backupIDExists := d.GetOk("backup_id") if !backupIDExists { backupName := d.Get("name").(string) + res, err := api.ListDatabaseBackups(&rdb.ListDatabaseBackupsRequest{ Region: region, Name: types.ExpandStringPtr(backupName), @@ -72,6 +73,7 @@ func DataSourceRDBDatabaseBackupRead(ctx context.Context, d *schema.ResourceData regionID := datasource.NewRegionalID(backupID, region) d.SetId(regionID) + err = d.Set("backup_id", regionID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/rdb/data_source_rdb_instance.go b/internal/services/rdb/data_source_rdb_instance.go index 6363828671..d508827494 100644 --- a/internal/services/rdb/data_source_rdb_instance.go +++ b/internal/services/rdb/data_source_rdb_instance.go @@ -42,6 +42,7 @@ func DataSourceRDBInstanceRead(ctx context.Context, d *schema.ResourceData, m in instanceID, ok := d.GetOk("instance_id") if !ok { // Get instance by region and name. instanceName := d.Get("name").(string) + res, err := api.ListInstances(&rdb.ListInstancesRequest{ Region: region, Name: scw.StringPtr(instanceName), @@ -65,6 +66,7 @@ func DataSourceRDBInstanceRead(ctx context.Context, d *schema.ResourceData, m in regionalID := datasource.NewRegionalID(instanceID, region) d.SetId(regionalID) + err = d.Set("instance_id", regionalID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/rdb/database.go b/internal/services/rdb/database.go index d5eed5f4b9..a2652832f8 100644 --- a/internal/services/rdb/database.go +++ b/internal/services/rdb/database.go @@ -84,6 +84,7 @@ func ResourceDatabase() *schema.Resource { func ResourceRdbDatabaseCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { rdbAPI := newAPI(m) + region, instanceID, err := regional.ParseID(d.Get("instance_id").(string)) if err != nil { return diag.FromErr(err) @@ -151,6 +152,7 @@ func getDatabase(ctx context.Context, api *rdb.API, r scw.Region, instanceID, db func ResourceRdbDatabaseRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { rdbAPI := newAPI(m) + region, instanceID, databaseName, err := ResourceRdbDatabaseParseID(d.Id()) if err != nil { return diag.FromErr(err) @@ -180,6 +182,7 @@ func ResourceRdbDatabaseRead(ctx context.Context, d *schema.ResourceData, m inte func ResourceRdbDatabaseDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { rdbAPI := newAPI(m) + region, instanceID, databaseName, err := ResourceRdbDatabaseParseID(d.Id()) if err != nil { return diag.FromErr(err) diff --git a/internal/services/rdb/database_backup_test.go b/internal/services/rdb/database_backup_test.go index b821c2b153..d28ec814d3 100644 --- a/internal/services/rdb/database_backup_test.go +++ b/internal/services/rdb/database_backup_test.go @@ -25,7 +25,9 @@ func init() { func testSweepDatabaseBackup(_ string) error { return acctest.SweepRegions(scw.AllRegions, func(scwClient *scw.Client, region scw.Region) error { rdbAPI := rdbSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the rdb database backups in (%s)", region) + listBackups, err := rdbAPI.ListDatabaseBackups(&rdbSDK.ListDatabaseBackupsRequest{ Region: region, }) diff --git a/internal/services/rdb/database_data_source.go b/internal/services/rdb/database_data_source.go index 698527d2ac..125be2b5d5 100644 --- a/internal/services/rdb/database_data_source.go +++ b/internal/services/rdb/database_data_source.go @@ -30,6 +30,7 @@ func DataSourceDatabaseRead(ctx context.Context, d *schema.ResourceData, m inter if err != nil { return diag.FromErr(err) } + instanceID, _ := d.GetOk("instance_id") dbName, _ := d.GetOk("name") @@ -39,6 +40,7 @@ func DataSourceDatabaseRead(ctx context.Context, d *schema.ResourceData, m inter } d.SetId(fmt.Sprintf("%s/%s", instanceID, dbName.(string))) + err = d.Set("instance_id", instanceID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/rdb/helpers.go b/internal/services/rdb/helpers.go index 370c06b04c..a34ff05ce3 100644 --- a/internal/services/rdb/helpers.go +++ b/internal/services/rdb/helpers.go @@ -85,6 +85,7 @@ func getIPConfigCreate(d *schema.ResourceData, ipFieldName string) (ipamConfig * if enableIpamSet { ipamConfig = types.ExpandBoolPtr(enableIpam) } + customIP, customIPSet := d.GetOk("private_network.0." + ipFieldName) if customIPSet { staticConfig = types.ExpandStringPtr(customIP) @@ -98,6 +99,7 @@ func getIPConfigUpdate(d *schema.ResourceData, ipFieldName string) (ipamConfig * if ipamConfigI, _ := meta.GetRawConfigForKey(d, "private_network.#.enable_ipam", cty.Bool); ipamConfigI != nil { ipamConfig = types.ExpandBoolPtr(ipamConfigI) } + if staticConfigI, _ := meta.GetRawConfigForKey(d, "private_network.#."+ipFieldName, cty.String); staticConfigI != nil { staticConfig = types.ExpandStringPtr(staticConfigI) } diff --git a/internal/services/rdb/instance.go b/internal/services/rdb/instance.go index 5347a79809..961a051a57 100644 --- a/internal/services/rdb/instance.go +++ b/internal/services/rdb/instance.go @@ -352,15 +352,19 @@ func ResourceRdbInstanceCreate(ctx context.Context, d *schema.ResourceData, m in // Init Endpoints if pn, pnExist := d.GetOk("private_network"); pnExist { ipamConfig, staticConfig := getIPConfigCreate(d, "ip_net") + var diags diag.Diagnostics + createReq.InitEndpoints, diags = expandPrivateNetwork(pn, pnExist, ipamConfig, staticConfig) if diags.HasError() { return diags } + for _, warning := range diags { tflog.Warn(ctx, warning.Detail) } } + if _, lbExists := d.GetOk("load_balancer"); lbExists { createReq.InitEndpoints = append(createReq.InitEndpoints, expandLoadBalancer()) } @@ -369,6 +373,7 @@ func ResourceRdbInstanceCreate(ctx context.Context, d *schema.ResourceData, m in if createReq.VolumeType == rdb.VolumeTypeLssd { return diag.FromErr(fmt.Errorf("volume_size_in_gb should not be used with volume_type %s", rdb.VolumeTypeLssd.String())) } + createReq.VolumeSize = scw.Size(uint64(size.(int)) * uint64(scw.GB)) } @@ -389,12 +394,15 @@ func ResourceRdbInstanceCreate(ctx context.Context, d *schema.ResourceData, m in if !d.Get("disable_backup").(bool) { updateReq.BackupSameRegion = types.ExpandBoolPtr(d.Get("backup_same_region")) updateReq.IsBackupScheduleDisabled = scw.BoolPtr(d.Get("disable_backup").(bool)) + if backupScheduleFrequency, okFrequency := d.GetOk("backup_schedule_frequency"); okFrequency { updateReq.BackupScheduleFrequency = scw.Uint32Ptr(uint32(backupScheduleFrequency.(int))) } + if backupScheduleRetention, okRetention := d.GetOk("backup_schedule_retention"); okRetention { updateReq.BackupScheduleRetention = scw.Uint32Ptr(uint32(backupScheduleRetention.(int))) } + mustUpdate = true } @@ -409,6 +417,7 @@ func ResourceRdbInstanceCreate(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } + _, err = rdbAPI.UpdateInstance(updateReq, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -451,6 +460,7 @@ func ResourceRdbInstanceRead(ctx context.Context, d *schema.ResourceData, m inte return diag.FromErr(err) } + _ = d.Set("name", res.Name) _ = d.Set("node_type", res.NodeType) _ = d.Set("engine", res.Engine) @@ -480,6 +490,7 @@ func ResourceRdbInstanceRead(ctx context.Context, d *schema.ResourceData, m inte default: _ = d.Set("endpoint_ip", "") } + _ = d.Set("endpoint_port", int(loadBalancerEndpoint.Port)) } else { _ = d.Set("endpoint_ip", "") @@ -490,10 +501,12 @@ func ResourceRdbInstanceRead(ctx context.Context, d *schema.ResourceData, m inte _ = d.Set("volume_type", res.Volume.Type) _ = d.Set("volume_size_in_gb", int(res.Volume.Size/scw.GB)) } + _ = d.Set("read_replicas", []string{}) _ = d.Set("region", string(region)) _ = d.Set("organization_id", res.OrganizationID) _ = d.Set("project_id", res.ProjectID) + if res.Encryption != nil { _ = d.Set("encryption_at_rest", res.Encryption.Enabled) } @@ -509,6 +522,7 @@ func ResourceRdbInstanceRead(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } + for _, u := range users.Users { if u.IsAdmin { _ = d.Set("user_name", u.Name) @@ -517,6 +531,7 @@ func ResourceRdbInstanceRead(ctx context.Context, d *schema.ResourceData, m inte } } } + _ = d.Set("password", d.Get("password").(string)) // set certificate @@ -527,10 +542,12 @@ func ResourceRdbInstanceRead(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } + certContent, err := io.ReadAll(cert.Content) if err != nil { return diag.FromErr(err) } + _ = d.Set("certificate", string(certContent)) // set settings @@ -544,6 +561,7 @@ func ResourceRdbInstanceRead(ctx context.Context, d *schema.ResourceData, m inte if pnI, pnExist := flattenPrivateNetwork(res.Endpoints); pnExist { _ = d.Set("private_network", pnI) } + if lbI, lbExists := flattenLoadBalancer(res.Endpoints); lbExists { _ = d.Set("load_balancer", lbI) } @@ -570,6 +588,7 @@ func ResourceRdbInstanceUpdate(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } + diskIsFull := rdbInstance.Status == rdb.InstanceStatusDiskFull volType := rdb.VolumeType(d.Get("volume_type").(string)) @@ -585,10 +604,12 @@ func ResourceRdbInstanceUpdate(ctx context.Context, d *schema.ResourceData, m in VolumeType: &volType, }) } + if d.HasChange("volume_size_in_gb") { oldSizeInterface, newSizeInterface := d.GetChange("volume_size_in_gb") oldSize := uint64(oldSizeInterface.(int)) newSize := uint64(newSizeInterface.(int)) + if newSize < oldSize { return diag.FromErr(errors.New("volume_size_in_gb cannot be decreased")) } @@ -609,6 +630,7 @@ func ResourceRdbInstanceUpdate(ctx context.Context, d *schema.ResourceData, m in if d.HasChange("volume_size_in_gb") && ok { return diag.FromErr(fmt.Errorf("volume_size_in_gb should be used with volume_type %s only", rdb.VolumeTypeBssd.String())) } + if d.HasChange("volume_type") { upgradeInstanceRequests = append(upgradeInstanceRequests, rdb.UpgradeInstanceRequest{ @@ -710,18 +732,23 @@ func ResourceRdbInstanceUpdate(ctx context.Context, d *schema.ResourceData, m in if d.HasChange("name") { req.Name = types.ExpandStringPtr(d.Get("name")) } + if d.HasChange("disable_backup") { req.IsBackupScheduleDisabled = scw.BoolPtr(d.Get("disable_backup").(bool)) } + if d.HasChange("backup_schedule_frequency") { req.BackupScheduleFrequency = scw.Uint32Ptr(uint32(d.Get("backup_schedule_frequency").(int))) } + if d.HasChange("backup_schedule_retention") { req.BackupScheduleRetention = scw.Uint32Ptr(uint32(d.Get("backup_schedule_retention").(int))) } + if d.HasChange("backup_same_region") { req.BackupSameRegion = types.ExpandBoolPtr(d.Get("backup_same_region")) } + if d.HasChange("tags") { req.Tags = types.ExpandUpdatedStringsPtr(d.Get("tags")) } @@ -751,6 +778,7 @@ func ResourceRdbInstanceUpdate(ctx context.Context, d *schema.ResourceData, m in if err != nil && !httperrors.Is404(err) { return diag.FromErr(err) } + _, err := rdbAPI.SetInstanceSettings(&rdb.SetInstanceSettingsRequest{ InstanceID: ID, Region: region, @@ -815,13 +843,16 @@ func ResourceRdbInstanceUpdate(ctx context.Context, d *schema.ResourceData, m in pn, pnExist := d.GetOk("private_network") if pnExist { ipamConfig, staticConfig := getIPConfigUpdate(d, "ip_net") + privateEndpoints, diags := expandPrivateNetwork(pn, pnExist, ipamConfig, staticConfig) if diags.HasError() { return diags } + for _, warning := range diags { tflog.Warn(ctx, warning.Detail) } + for _, e := range privateEndpoints { _, err := rdbAPI.CreateEndpoint( &rdb.CreateEndpointRequest{Region: region, InstanceID: ID, EndpointSpec: e}, @@ -832,6 +863,7 @@ func ResourceRdbInstanceUpdate(ctx context.Context, d *schema.ResourceData, m in } } } + if d.HasChanges("load_balancer") { // retrieve state res, err := waitForRDBInstance(ctx, rdbAPI, region, ID, d.Timeout(schema.TimeoutUpdate)) diff --git a/internal/services/rdb/privilege.go b/internal/services/rdb/privilege.go index 7c39565845..e7f35adb22 100644 --- a/internal/services/rdb/privilege.go +++ b/internal/services/rdb/privilege.go @@ -75,6 +75,7 @@ func ResourceRdbPrivilegeCreate(ctx context.Context, d *schema.ResourceData, m i } instanceID := locality.ExpandID(d.Get("instance_id").(string)) + _, err = waitForRDBInstance(ctx, api, region, instanceID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -181,6 +182,7 @@ func ResourceRdbPrivilegeRead(ctx context.Context, d *schema.ResourceData, m int if len(res.Privileges) == 0 { return diag.FromErr(fmt.Errorf("couldn't retrieve privileges for user[%s] on database [%s]", userName, databaseName)) } + privilege := res.Privileges[0] _ = d.Set("database_name", privilege.DatabaseName) _ = d.Set("user_name", privilege.UserName) @@ -193,10 +195,12 @@ func ResourceRdbPrivilegeRead(ctx context.Context, d *schema.ResourceData, m int func ResourceRdbPrivilegeUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { rdbAPI := newAPI(m) + region, instanceID, databaseName, userName, err := ResourceRdbUserPrivilegeParseID(d.Id()) if err != nil { return diag.FromErr(err) } + _, err = waitForRDBInstance(ctx, rdbAPI, region, instanceID, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) @@ -264,6 +268,7 @@ func ResourceRdbPrivilegeUpdate(ctx context.Context, d *schema.ResourceData, m i //gocyclo:ignore func ResourceRdbPrivilegeDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { rdbAPI := newAPI(m) + region, instanceID, databaseName, userName, err := ResourceRdbUserPrivilegeParseID(d.Id()) if err != nil { return diag.FromErr(err) @@ -275,6 +280,7 @@ func ResourceRdbPrivilegeDelete(ctx context.Context, d *schema.ResourceData, m i } _ = d.Set("permission", rdb.PermissionNone) + listUsers, err := rdbAPI.ListUsers(&rdb.ListUsersRequest{ Region: region, InstanceID: instanceID, @@ -312,6 +318,7 @@ func ResourceRdbPrivilegeDelete(ctx context.Context, d *schema.ResourceData, m i InstanceID: instanceID, Name: &userName, }, scw.WithContext(ctx)) + if err != nil { if httperrors.Is404(err) { d.SetId("") @@ -327,6 +334,7 @@ func ResourceRdbPrivilegeDelete(ctx context.Context, d *schema.ResourceData, m i return nil } + _, errSet := rdbAPI.SetPrivilege(updateReq, scw.WithContext(ctx)) if errSet != nil { if httperrors.Is409(errSet) { diff --git a/internal/services/rdb/read_replica.go b/internal/services/rdb/read_replica.go index 3eb366bbb9..9bbb62f6a9 100644 --- a/internal/services/rdb/read_replica.go +++ b/internal/services/rdb/read_replica.go @@ -171,9 +171,11 @@ func ResourceRdbReadReplicaCreate(ctx context.Context, d *schema.ResourceData, m if diags.HasError() { return diags } + for _, warning := range diags { tflog.Warn(ctx, warning.Detail) } + endpointSpecs = append(endpointSpecs, pn) } @@ -292,13 +294,16 @@ func ResourceRdbReadReplicaUpdate(ctx context.Context, d *schema.ResourceData, m // create a new one if defined if pn, pnExists := d.GetOk("private_network"); pnExists { ipamConfig, staticConfig := getIPConfigUpdate(d, "service_ip") + pnEndpoint, diags := expandReadReplicaEndpointsSpecPrivateNetwork(pn, ipamConfig, staticConfig) if diags.HasError() { return diags } + for _, warning := range diags { tflog.Warn(ctx, warning.Detail) } + newEndpoints = append(newEndpoints, pnEndpoint) } } @@ -308,6 +313,7 @@ func ResourceRdbReadReplicaUpdate(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + _, err = rdbAPI.CreateReadReplicaEndpoint(&rdb.CreateReadReplicaEndpointRequest{ Region: region, ReadReplicaID: ID, diff --git a/internal/services/rdb/testfuncs/checks.go b/internal/services/rdb/testfuncs/checks.go index 99b9f52ed4..ea5e37bb73 100644 --- a/internal/services/rdb/testfuncs/checks.go +++ b/internal/services/rdb/testfuncs/checks.go @@ -45,12 +45,14 @@ func IsInstanceDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { func GetLatestEngineVersion(tt *acctest.TestTools, engineName string) string { api := rdbSDK.NewAPI(tt.Meta.ScwClient()) + engines, err := api.ListDatabaseEngines(&rdbSDK.ListDatabaseEnginesRequest{}) if err != nil { tt.T.Fatalf("Could not get latest engine version: %s", err) } latestEngineVersion := "" + for _, engine := range engines.Engines { if engine.Name == engineName { if len(engine.Versions) > 0 { diff --git a/internal/services/rdb/testfuncs/sweep.go b/internal/services/rdb/testfuncs/sweep.go index b567c4ce74..ab14723316 100644 --- a/internal/services/rdb/testfuncs/sweep.go +++ b/internal/services/rdb/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepInstance(_ string) error { return acctest.SweepRegions(scw.AllRegions, func(scwClient *scw.Client, region scw.Region) error { rdbAPI := rdbSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the rdb instance in (%s)", region) + listInstances, err := rdbAPI.ListInstances(&rdbSDK.ListInstancesRequest{ Region: region, }, scw.WithAllPages()) diff --git a/internal/services/rdb/types.go b/internal/services/rdb/types.go index 3e0a2912ca..37eb4e2540 100644 --- a/internal/services/rdb/types.go +++ b/internal/services/rdb/types.go @@ -23,6 +23,7 @@ func flattenInstanceSettings(settings []*rdb.InstanceSetting) interface{} { func expandInstanceSettings(i interface{}) []*rdb.InstanceSetting { rawRule := i.(map[string]interface{}) res := make([]*rdb.InstanceSetting, 0, len(rawRule)) + for key, value := range rawRule { res = append(res, &rdb.InstanceSetting{ Name: key, @@ -37,9 +38,11 @@ func expandPrivateNetwork(data interface{}, exist bool, ipamConfig *bool, static if data == nil || !exist { return nil, nil } + var diags diag.Diagnostics res := make([]*rdb.EndpointSpec, 0, len(data.([]interface{}))) + for _, pn := range data.([]interface{}) { r := pn.(map[string]interface{}) spec := &rdb.EndpointSpec{ @@ -54,8 +57,10 @@ func expandPrivateNetwork(data interface{}, exist bool, ipamConfig *bool, static if err != nil { return nil, append(diags, diag.FromErr(fmt.Errorf("failed to parse private_network ip_net (%s): %s", r["ip_net"], err))...) } + spec.PrivateNetwork.ServiceIP = &ip spec.PrivateNetwork.IpamConfig = nil + if ipamConfig != nil && *ipamConfig { diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, @@ -65,6 +70,7 @@ func expandPrivateNetwork(data interface{}, exist bool, ipamConfig *bool, static } else if ipamConfig == nil || !*ipamConfig { return nil, diag.FromErr(errors.New("at least one of `ip_net` or `enable_ipam` (set to true) must be set")) } + res = append(res, spec) } @@ -79,22 +85,28 @@ func expandLoadBalancer() *rdb.EndpointSpec { func flattenPrivateNetwork(endpoints []*rdb.Endpoint) (interface{}, bool) { pnI := []map[string]interface{}(nil) + for _, endpoint := range endpoints { if endpoint.PrivateNetwork != nil { pn := endpoint.PrivateNetwork + fetchRegion, err := pn.Zone.Region() if err != nil { return diag.FromErr(err), false } + pnRegionalID := regional.NewIDString(fetchRegion, pn.PrivateNetworkID) + serviceIP, err := types.FlattenIPNet(pn.ServiceIP) if err != nil { return pnI, false } + enableIpam := false if endpoint.PrivateNetwork.ProvisioningMode == rdb.EndpointPrivateNetworkDetailsProvisioningModeIpam { enableIpam = true } + pnI = append(pnI, map[string]interface{}{ "endpoint_id": endpoint.ID, "ip": types.FlattenIPPtr(endpoint.IP), @@ -115,6 +127,7 @@ func flattenPrivateNetwork(endpoints []*rdb.Endpoint) (interface{}, bool) { func flattenLoadBalancer(endpoints []*rdb.Endpoint) (interface{}, bool) { flat := []map[string]interface{}(nil) + for _, endpoint := range endpoints { if endpoint.LoadBalancer != nil { flat = append(flat, map[string]interface{}{ @@ -151,6 +164,7 @@ func expandReadReplicaEndpointsSpecPrivateNetwork(data interface{}, ipamConfig * data = data.([]interface{})[0] rawEndpoint := data.(map[string]interface{}) + var diags diag.Diagnostics endpoint := &rdb.ReadReplicaEndpointSpec{ @@ -165,8 +179,10 @@ func expandReadReplicaEndpointsSpecPrivateNetwork(data interface{}, ipamConfig * if err != nil { return nil, append(diags, diag.FromErr(fmt.Errorf("failed to parse private_network service_ip (%s): %s", rawEndpoint["service_ip"], err))...) } + endpoint.PrivateNetwork.ServiceIP = &ipNet endpoint.PrivateNetwork.IpamConfig = nil + if ipamConfig != nil && !*ipamConfig { diags = append(diags, diag.Diagnostic{ Severity: diag.Warning, @@ -193,16 +209,20 @@ func flattenReadReplicaEndpoints(endpoints []*rdb.Endpoint) (directAccess, priva if endpoint.DirectAccess != nil { directAccess = rawEndpoint } + if endpoint.PrivateNetwork != nil { fetchRegion, err := endpoint.PrivateNetwork.Zone.Region() if err != nil { return diag.FromErr(err), false } + pnRegionalID := regional.NewIDString(fetchRegion, endpoint.PrivateNetwork.PrivateNetworkID) + enableIpam := false if endpoint.PrivateNetwork.ProvisioningMode == rdb.EndpointPrivateNetworkDetailsProvisioningModeIpam { enableIpam = true } + rawEndpoint["private_network_id"] = pnRegionalID rawEndpoint["service_ip"] = endpoint.PrivateNetwork.ServiceIP.String() rawEndpoint["zone"] = endpoint.PrivateNetwork.Zone @@ -216,6 +236,7 @@ func flattenReadReplicaEndpoints(endpoints []*rdb.Endpoint) (directAccess, priva if directAccess != nil { directAccess = []interface{}{directAccess} } + if privateNetwork != nil { privateNetwork = []interface{}{privateNetwork} } diff --git a/internal/services/rdb/user.go b/internal/services/rdb/user.go index 73888ef995..5588df48bf 100644 --- a/internal/services/rdb/user.go +++ b/internal/services/rdb/user.go @@ -72,6 +72,7 @@ func ResourceUserCreate(ctx context.Context, d *schema.ResourceData, m interface rdbAPI := newAPI(m) // resource depends on the instance locality regionalID := d.Get("instance_id").(string) + region, instanceID, err := regional.ParseID(regionalID) if err != nil { diag.FromErr(err) @@ -122,6 +123,7 @@ func ResourceUserCreate(ctx context.Context, d *schema.ResourceData, m interface func ResourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { rdbAPI := newAPI(m) + region, instanceID, userName, err := ResourceUserParseID(d.Id()) if err != nil { return diag.FromErr(err) @@ -152,6 +154,7 @@ func ResourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{} return diag.FromErr(err) } + if len(res.Users) == 0 { tflog.Warn(ctx, fmt.Sprintf("couldn'd find user with name: [%s]", userName)) d.SetId("") @@ -192,6 +195,7 @@ func ResourceUserUpdate(ctx context.Context, d *schema.ResourceData, m interface if d.HasChange("password") { req.Password = types.ExpandStringPtr(d.Get("password")) } + if d.HasChange("is_admin") { req.IsAdmin = scw.BoolPtr(d.Get("is_admin").(bool)) } diff --git a/internal/services/redis/cluster.go b/internal/services/redis/cluster.go index d4ae102313..d347de7a83 100644 --- a/internal/services/redis/cluster.go +++ b/internal/services/redis/cluster.go @@ -253,22 +253,27 @@ func ResourceClusterCreate(ctx context.Context, d *schema.ResourceData, m interf if tagsExist { createReq.Tags = types.ExpandStrings(tags) } + clusterSize, clusterSizeExist := d.GetOk("cluster_size") if clusterSizeExist { createReq.ClusterSize = scw.Int32Ptr(int32(clusterSize.(int))) } + tlsEnabled, tlsEnabledExist := d.GetOk("tls_enabled") if tlsEnabledExist { createReq.TLSEnabled = tlsEnabled.(bool) } + aclRules, aclRulesExist := d.GetOk("acl") if aclRulesExist { rules, err := expandACLSpecs(aclRules) if err != nil { return diag.FromErr(err) } + createReq.ACLRules = rules } + settings, settingsExist := d.GetOk("settings") if settingsExist { createReq.ClusterSettings = expandSettings(settings) @@ -280,6 +285,7 @@ func ResourceClusterCreate(ctx context.Context, d *schema.ResourceData, m interf if err != nil { return diag.FromErr(err) } + createReq.Endpoints = pnSpecs } @@ -308,6 +314,7 @@ func ResourceClusterRead(ctx context.Context, d *schema.ResourceData, m interfac Zone: zone, ClusterID: ID, } + cluster, err := redisAPI.GetCluster(getReq, scw.WithContext(ctx)) if err != nil { if httperrors.Is404(err) { @@ -341,6 +348,7 @@ func ResourceClusterRead(ctx context.Context, d *schema.ResourceData, m interfac if pnExists { _ = d.Set("private_network", pnI) } + _ = d.Set("public_network", flattenPublicNetwork(cluster.Endpoints)) if cluster.TLSEnabled { @@ -379,21 +387,26 @@ func ResourceClusterUpdate(ctx context.Context, d *schema.ResourceData, m interf if d.HasChange("name") { req.Name = types.ExpandStringPtr(d.Get("name")) } + if d.HasChange("user_name") { req.UserName = types.ExpandStringPtr(d.Get("user_name")) } + if d.HasChange("password") { req.Password = types.ExpandStringPtr(d.Get("password")) } + if d.HasChange("tags") { req.Tags = types.ExpandUpdatedStringsPtr(d.Get("tags")) } + if d.HasChange("acl") { diagnostics := updateACL(ctx, d, redisAPI, zone, ID) if diagnostics != nil { return diagnostics } } + if d.HasChange("settings") { diagnostics := updateSettings(ctx, d, redisAPI, zone, ID) if diagnostics != nil { @@ -419,6 +432,7 @@ func ResourceClusterUpdate(ctx context.Context, d *schema.ResourceData, m interf ClusterSize: scw.Uint32Ptr(uint32(d.Get("cluster_size").(int))), }) } + if d.HasChange("version") { migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{ Zone: zone, @@ -426,6 +440,7 @@ func ResourceClusterUpdate(ctx context.Context, d *schema.ResourceData, m interf Version: types.ExpandStringPtr(d.Get("version")), }) } + if d.HasChange("node_type") { migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{ Zone: zone, @@ -433,11 +448,13 @@ func ResourceClusterUpdate(ctx context.Context, d *schema.ResourceData, m interf NodeType: types.ExpandStringPtr(d.Get("node_type")), }) } + for i := range migrateClusterRequests { _, err = waitForCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate)) if err != nil && !httperrors.Is404(err) { return diag.FromErr(err) } + _, err = redisAPI.MigrateCluster(&migrateClusterRequests[i], scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -506,10 +523,12 @@ func ResourceClusterUpdateEndpoints(ctx context.Context, d *schema.ResourceData, // get new desired state of endpoints rawNewEndpoints := d.Get("private_network") + newEndpoints, err := expandPrivateNetwork(rawNewEndpoints.(*schema.Set).List()) if err != nil { return diag.FromErr(err) } + if len(newEndpoints) == 0 { newEndpoints = append(newEndpoints, &redis.EndpointSpec{ PublicNetwork: &redis.EndpointSpecPublicNetworkSpec{}, diff --git a/internal/services/redis/cluster_data_source.go b/internal/services/redis/cluster_data_source.go index 6ee8e2cfb6..2380df0bcd 100644 --- a/internal/services/redis/cluster_data_source.go +++ b/internal/services/redis/cluster_data_source.go @@ -44,6 +44,7 @@ func DataSourceClusterRead(ctx context.Context, d *schema.ResourceData, m interf clusterID, ok := d.GetOk("cluster_id") if !ok { clusterName := d.Get("name").(string) + res, err := api.ListClusters(&redis.ListClustersRequest{ Zone: zone, Name: types.ExpandStringPtr(clusterName), @@ -67,6 +68,7 @@ func DataSourceClusterRead(ctx context.Context, d *schema.ResourceData, m interf zonedID := datasource.NewZonedID(clusterID, zone) d.SetId(zonedID) + err = d.Set("cluster_id", zonedID) if err != nil { return diag.FromErr(err) @@ -78,6 +80,7 @@ func DataSourceClusterRead(ctx context.Context, d *schema.ResourceData, m interf Zone: zone, ClusterID: locality.ExpandID(clusterID.(string)), } + _, err = api.GetCluster(getReq, scw.WithContext(ctx)) if err != nil { return diag.FromErr(fmt.Errorf("no clusters found with the id %s", clusterID)) diff --git a/internal/services/redis/cluster_test.go b/internal/services/redis/cluster_test.go index 166c835e2c..5de3a41ab7 100644 --- a/internal/services/redis/cluster_test.go +++ b/internal/services/redis/cluster_test.go @@ -374,6 +374,7 @@ func TestAccCluster_Settings(t *testing.T) { func TestAccCluster_Endpoints_Standalone(t *testing.T) { t.Skip("TestAccCluster_Endpoints_Standalone skipped: API issue causes instance status to transition to 'error' during private network deletion.") + tt := acctest.NewTestTools(t) defer tt.Cleanup() latestRedisVersion := getLatestVersion(tt) @@ -797,10 +798,12 @@ func privateNetworksIpsAreEither(name string, possibilities ...string) resource. if !ok { return fmt.Errorf("resource not found: %s", name) } + actualIPs := []string(nil) for i := range possibilities { actualIPs = append(actualIPs, rs.Primary.Attributes[fmt.Sprintf("private_network.%d.service_ips.0", i)]) } + for _, ip := range actualIPs { for i := range possibilities { if possibilities[i] == ip { @@ -808,6 +811,7 @@ func privateNetworksIpsAreEither(name string, possibilities ...string) resource. } } } + for _, p := range possibilities { if p != "ip found" { return fmt.Errorf("no attribute private_network.*.service_ips.0 was found with value %v", p) @@ -824,18 +828,22 @@ func privateNetworksIDsAreEither(name string, possibilities ...string) resource. if !ok { return fmt.Errorf("resource not found: %s", name) } + for i, possibility := range possibilities { rs, ok := state.RootModule().Resources[possibility] if ok { possibilities[i] = rs.Primary.ID } } + actualIDs := []string(nil) + for i := range possibilities { toLookFor := fmt.Sprintf("private_network.%d.id", i) id := rs.Primary.Attributes[toLookFor] actualIDs = append(actualIDs, id) } + for _, id := range actualIDs { for i := range possibilities { if possibilities[i] == id { @@ -843,6 +851,7 @@ func privateNetworksIDsAreEither(name string, possibilities ...string) resource. } } } + for _, p := range possibilities { if p != "id found" { return fmt.Errorf("no attribute private_network.*.id was found with value %v", p) @@ -859,11 +868,14 @@ func isCertificateValid(name string) resource.TestCheckFunc { if !ok { return fmt.Errorf("resource not found: %s", name) } + pemCert, hasCert := rs.Primary.Attributes["certificate"] if !hasCert { return errors.New("could not find certificate in schema") } + cert, _ := pem.Decode([]byte(pemCert)) + _, err := x509.ParseCertificate(cert.Bytes) if err != nil { return fmt.Errorf("failed to parse certificate: %w", err) @@ -880,6 +892,7 @@ func getLatestVersion(tt *acctest.TestTools) string { if err != nil { tt.T.Fatalf("Could not get latestK8SVersion: %s", err) } + if len(versions.Versions) > 0 { latestRedisVersion := versions.Versions[0].Version diff --git a/internal/services/redis/testfuncs/sweep.go b/internal/services/redis/testfuncs/sweep.go index 6ee1ba4902..29b0fa9035 100644 --- a/internal/services/redis/testfuncs/sweep.go +++ b/internal/services/redis/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepRedisCluster(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { redisAPI := redisSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the redis cluster in (%s)", zone) + listClusters, err := redisAPI.ListClusters(&redisSDK.ListClustersRequest{ Zone: zone, }, scw.WithAllPages()) diff --git a/internal/services/redis/types.go b/internal/services/redis/types.go index d67474fe12..8a2e1ba586 100644 --- a/internal/services/redis/types.go +++ b/internal/services/redis/types.go @@ -16,6 +16,7 @@ func expandPrivateNetwork(data []interface{}) ([]*redis.EndpointSpec, error) { if data == nil { return nil, nil } + epSpecs := make([]*redis.EndpointSpec, 0, len(data)) for _, rawPN := range data { @@ -26,14 +27,17 @@ func expandPrivateNetwork(data []interface{}) ([]*redis.EndpointSpec, error) { spec := &redis.EndpointSpecPrivateNetworkSpec{ ID: pnID, } + if len(rawIPs) != 0 { for _, rawIP := range rawIPs { ip, err := types.ExpandIPNet(rawIP.(string)) if err != nil { return epSpecs, err } + ips = append(ips, ip) } + spec.ServiceIPs = ips } else { spec.IpamConfig = &redis.EndpointSpecPrivateNetworkSpecIpamConfig{} @@ -51,13 +55,16 @@ func expandACLSpecs(i interface{}) ([]*redis.ACLRuleSpec, error) { for _, aclRule := range i.(*schema.Set).List() { rawRule := aclRule.(map[string]interface{}) rule := &redis.ACLRuleSpec{} + if ruleDescription, hasDescription := rawRule["description"]; hasDescription { rule.Description = ruleDescription.(string) } + ip, err := types.ExpandIPNet(rawRule["ip"].(string)) if err != nil { return nil, fmt.Errorf("failed to validate acl ip (%s): %w", rawRule["ip"].(string), err) } + rule.IPCidr = ip rules = append(rules, rule) } @@ -81,6 +88,7 @@ func flattenACLs(aclRules []*redis.ACLRule) interface{} { func expandSettings(i interface{}) []*redis.ClusterSetting { rawSettings := i.(map[string]interface{}) settings := []*redis.ClusterSetting(nil) + for key, value := range rawSettings { settings = append(settings, &redis.ClusterSetting{ Name: key, @@ -102,20 +110,26 @@ func flattenSettings(settings []*redis.ClusterSetting) interface{} { func flattenPrivateNetwork(endpoints []*redis.Endpoint) (interface{}, bool) { pnFlat := []map[string]interface{}(nil) + for _, endpoint := range endpoints { if endpoint.PrivateNetwork == nil { continue } + pn := endpoint.PrivateNetwork + fetchRegion, err := pn.Zone.Region() if err != nil { return diag.FromErr(err), false } + pnRegionalID := regional.NewIDString(fetchRegion, pn.ID) + serviceIps := []interface{}(nil) for _, ip := range pn.ServiceIPs { serviceIps = append(serviceIps, ip.String()) } + pnFlat = append(pnFlat, map[string]interface{}{ "endpoint_id": endpoint.ID, "zone": pn.Zone, @@ -129,14 +143,17 @@ func flattenPrivateNetwork(endpoints []*redis.Endpoint) (interface{}, bool) { func flattenPublicNetwork(endpoints []*redis.Endpoint) interface{} { pnFlat := []map[string]interface{}(nil) + for _, endpoint := range endpoints { if endpoint.PublicNetwork == nil { continue } + ipsFlat := []interface{}(nil) for _, ip := range endpoint.IPs { ipsFlat = append(ipsFlat, ip.String()) } + pnFlat = append(pnFlat, map[string]interface{}{ "id": endpoint.ID, "port": int(endpoint.Port), diff --git a/internal/services/registry/image_data_source.go b/internal/services/registry/image_data_source.go index 7bbfe672f2..bcc91eff5d 100644 --- a/internal/services/registry/image_data_source.go +++ b/internal/services/registry/image_data_source.go @@ -77,13 +77,16 @@ func DataSourceRegistryImageRead(ctx context.Context, d *schema.ResourceData, m } var image *registry.Image + imageID, ok := d.GetOk("image_id") if !ok { var namespaceID *string if d.Get("namespace_id") != "" { namespaceID = types.ExpandStringPtr(locality.ExpandID(d.Get("namespace_id"))) } + imageName := d.Get("name").(string) + res, err := api.ListImages(®istry.ListImagesRequest{ Region: region, Name: types.ExpandStringPtr(imageName), @@ -93,6 +96,7 @@ func DataSourceRegistryImageRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + foundImage, err := datasource.FindExact( res.Images, func(s *registry.Image) bool { return s.Name == imageName }, @@ -111,6 +115,7 @@ func DataSourceRegistryImageRead(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + image = res } diff --git a/internal/services/registry/image_data_source_test.go b/internal/services/registry/image_data_source_test.go index d8d2ea8d81..235b5969dd 100644 --- a/internal/services/registry/image_data_source_test.go +++ b/internal/services/registry/image_data_source_test.go @@ -13,8 +13,10 @@ import ( func TestAccDataSourceImage_Basic(t *testing.T) { t.Skip("It is difficult to test this datasource as we cannot create registry images with Terraform.") + tt := acctest.NewTestTools(t) defer tt.Cleanup() + ubuntuImageID := "4b5a47c0-6fbf-4388-8783-c07c28d3c2eb" resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/registry/namespace_data_source.go b/internal/services/registry/namespace_data_source.go index ec238134d5..8a3717f0f5 100644 --- a/internal/services/registry/namespace_data_source.go +++ b/internal/services/registry/namespace_data_source.go @@ -42,6 +42,7 @@ func DataSourceNamespaceRead(ctx context.Context, d *schema.ResourceData, m inte namespaceID, ok := d.GetOk("namespace_id") if !ok { namespaceName := d.Get("name").(string) + res, err := api.ListNamespaces(®istry.ListNamespacesRequest{ Region: region, Name: types.ExpandStringPtr(namespaceName), diff --git a/internal/services/registry/tag_data_source.go b/internal/services/registry/tag_data_source.go index b41ad44551..3f3bd7d684 100644 --- a/internal/services/registry/tag_data_source.go +++ b/internal/services/registry/tag_data_source.go @@ -73,6 +73,7 @@ func DataSourceImageTagRead(ctx context.Context, d *schema.ResourceData, m inter } var tag *registry.Tag + tagID, tagIDExists := d.GetOk("tag_id") imageID := d.Get("image_id").(string) @@ -84,6 +85,7 @@ func DataSourceImageTagRead(ctx context.Context, d *schema.ResourceData, m inter if err != nil { return diag.FromErr(err) } + tag = res } else { tagName, nameExists := d.GetOk("name") diff --git a/internal/services/registry/tag_data_source_test.go b/internal/services/registry/tag_data_source_test.go index f782c81fe2..992b231b98 100644 --- a/internal/services/registry/tag_data_source_test.go +++ b/internal/services/registry/tag_data_source_test.go @@ -134,10 +134,12 @@ func deleteImage(tt *acctest.TestTools, n string) resource.TestCheckFunc { if !ok { return fmt.Errorf("resource not found: %s", n) } + api, region, _, err := registry.NewAPIWithRegionAndID(tt.Meta, rs.Primary.ID) if err != nil { return err } + _, err = api.DeleteImage(®istrySDK.DeleteImageRequest{ Region: region, ImageID: locality.ExpandID(rs.Primary.ID), diff --git a/internal/services/registry/testfuncs/checks.go b/internal/services/registry/testfuncs/checks.go index c6aef1c2df..057cc36cff 100644 --- a/internal/services/registry/testfuncs/checks.go +++ b/internal/services/registry/testfuncs/checks.go @@ -27,6 +27,7 @@ func PushImageToRegistry(tt *acctest.TestTools, registryEndpoint string, tagName } meta := tt.Meta + var errorMessage registry.ErrorRegistryMessage accessKey, _ := meta.ScwClient().GetAccessKey() @@ -58,11 +59,13 @@ func PushImageToRegistry(tt *acctest.TestTools, registryEndpoint string, tagName defer out.Close() buffIOReader := bufio.NewReader(out) + for { streamBytes, errPull := buffIOReader.ReadBytes('\n') if errPull == io.EOF { break } + err = json.Unmarshal(streamBytes, &errorMessage) if err != nil { return fmt.Errorf("could not unmarshal: %v", err) @@ -74,6 +77,7 @@ func PushImageToRegistry(tt *acctest.TestTools, registryEndpoint string, tagName } scwTag := registryEndpoint + "/alpine:" + tagName + err = cli.ImageTag(ctx, testDockerIMG, scwTag) if err != nil { return fmt.Errorf("could not tag image: %v", err) @@ -86,11 +90,13 @@ func PushImageToRegistry(tt *acctest.TestTools, registryEndpoint string, tagName defer pusher.Close() buffIOReader = bufio.NewReader(pusher) + for { streamBytes, errPush := buffIOReader.ReadBytes('\n') if errPush == io.EOF { break } + err = json.Unmarshal(streamBytes, &errorMessage) if err != nil { return fmt.Errorf("could not unmarshal: %v", err) diff --git a/internal/services/registry/testfuncs/sweep.go b/internal/services/registry/testfuncs/sweep.go index e4f7bf0013..106bcac4ff 100644 --- a/internal/services/registry/testfuncs/sweep.go +++ b/internal/services/registry/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepNamespace(_ string) error { return acctest.SweepRegions([]scw.Region{scw.RegionFrPar, scw.RegionNlAms}, func(scwClient *scw.Client, region scw.Region) error { registryAPI := registrySDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the registry namespaces in (%s)", region) + listNamespaces, err := registryAPI.ListNamespaces( ®istrySDK.ListNamespacesRequest{Region: region}, scw.WithAllPages()) if err != nil { diff --git a/internal/services/registry/waiters.go b/internal/services/registry/waiters.go index 9714c9212f..5ac6599560 100644 --- a/internal/services/registry/waiters.go +++ b/internal/services/registry/waiters.go @@ -41,6 +41,7 @@ func waitForNamespaceDelete(ctx context.Context, api *registry.API, region scw.R } start := time.Now() + for { ns, err := api.GetNamespace(®istry.GetNamespaceRequest{ Region: region, diff --git a/internal/services/scwconfig/scw_config_data_source.go b/internal/services/scwconfig/scw_config_data_source.go index 05e7140bf7..a25e3fe7a4 100644 --- a/internal/services/scwconfig/scw_config_data_source.go +++ b/internal/services/scwconfig/scw_config_data_source.go @@ -60,6 +60,7 @@ func DataSourceConfig() *schema.Resource { func dataSourceConfigRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := meta.ExtractScwClient(m) providerMeta := m.(*meta.Meta) + d.SetId("0") accessKey, _ := client.GetAccessKey() diff --git a/internal/services/scwconfig/scw_config_data_source_test.go b/internal/services/scwconfig/scw_config_data_source_test.go index 9832d15fa2..bf5cd3e8c5 100644 --- a/internal/services/scwconfig/scw_config_data_source_test.go +++ b/internal/services/scwconfig/scw_config_data_source_test.go @@ -20,6 +20,7 @@ func TestAccDataSourceConfig_ActiveProfile(t *testing.T) { if accessKey := os.Getenv("SCW_ACCESS_KEY"); accessKey == ciAccessKey { t.Skip("Skipping TestAccDataSourceConfig_ActiveProfile") } + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -71,6 +72,7 @@ func TestAccDataSourceConfig_OtherProfile(t *testing.T) { if accessKey := os.Getenv("SCW_ACCESS_KEY"); accessKey == ciAccessKey { t.Skip("Skipping TestAccDataSourceConfig_OtherProfile") } + tt := acctest.NewTestTools(t) defer tt.Cleanup() @@ -123,6 +125,7 @@ func TestAccDataSourceConfig_MixedProfile(t *testing.T) { if accessKey := os.Getenv("SCW_ACCESS_KEY"); accessKey == ciAccessKey { t.Skip("Skipping TestAccDataSourceConfig_MixedProfile") } + tt := acctest.NewTestTools(t) defer tt.Cleanup() diff --git a/internal/services/sdb/database.go b/internal/services/sdb/database.go index 89ce5ac49d..0655e5422e 100644 --- a/internal/services/sdb/database.go +++ b/internal/services/sdb/database.go @@ -140,6 +140,7 @@ func ResourceDatabaseUpdate(ctx context.Context, d *schema.ResourceData, m inter if d.HasChange("max_cpu") { req.CPUMax = types.ExpandUint32Ptr(d.Get("max_cpu")) } + if d.HasChange("min_cpu") { req.CPUMin = types.ExpandUint32Ptr(d.Get("min_cpu")) } diff --git a/internal/services/sdb/testfuncs/sweep.go b/internal/services/sdb/testfuncs/sweep.go index bd36e19447..b5bd385e07 100644 --- a/internal/services/sdb/testfuncs/sweep.go +++ b/internal/services/sdb/testfuncs/sweep.go @@ -20,7 +20,9 @@ func AddTestSweepers() { func testSweepServerlessSQLDBDatabase(_ string) error { return acctest.SweepRegions((&sdbSDK.API{}).Regions(), func(scwClient *scw.Client, region scw.Region) error { sdbAPI := sdbSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the serverless sql database in (%s)", region) + listServerlessSQLDBDatabases, err := sdbAPI.ListDatabases( &sdbSDK.ListDatabasesRequest{ Region: region, diff --git a/internal/services/secret/helpers.go b/internal/services/secret/helpers.go index fcfdf200c2..b2659f1b25 100644 --- a/internal/services/secret/helpers.go +++ b/internal/services/secret/helpers.go @@ -43,6 +43,7 @@ func newAPIWithRegionOptionalProjectIDAndDefault(d *schema.ResourceData, m inter } var projectIDPtr *string + projectID, _, err := meta.ExtractProjectID(d, m) if err == nil { projectIDPtr = &projectID @@ -61,6 +62,7 @@ func newAPIWithRegionAndProjectID(d *schema.ResourceData, m interface{}) (*secre } var projectIDPtr *string + projectID, _, err := meta.ExtractProjectID(d, m) if err == nil { projectIDPtr = &projectID @@ -87,6 +89,7 @@ func NewVersionAPIWithRegionAndID(m interface{}, id string) (*secret.API, scw.Re if err != nil { return nil, "", "", "", err } + api := secret.NewAPI(meta.ExtractScwClient(m)) return api, scw.Region(region), id, revision, nil @@ -146,6 +149,7 @@ func expandEphemeralPolicy(rawSchemaPolicy any) (*secret.EphemeralPolicy, error) if len(rawList) != 1 { return nil, fmt.Errorf("expected 1 policy, found %d", len(rawList)) } + rawPolicy := rawList[0].(map[string]interface{}) ttl, err := types.ExpandDuration(rawPolicy["ttl"]) @@ -169,13 +173,16 @@ func flattenEphemeralPolicy(policy *secret.EphemeralPolicy) []map[string]interfa if policy == nil { return nil } + policyElem := map[string]interface{}{} if policy.TimeToLive != nil { policyElem["ttl"] = types.FlattenDuration(policy.TimeToLive.ToTimeDuration()) } + if policy.ExpiresOnceAccessed != nil { policyElem["expires_once_accessed"] = types.FlattenBoolPtr(policy.ExpiresOnceAccessed) } + policyElem["action"] = policy.Action return []map[string]interface{}{policyElem} diff --git a/internal/services/secret/secret.go b/internal/services/secret/secret.go index 9aa9f47c6c..c2bf689f0d 100644 --- a/internal/services/secret/secret.go +++ b/internal/services/secret/secret.go @@ -246,6 +246,7 @@ func ResourceSecretUpdate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + hasChanged = true } diff --git a/internal/services/secret/secret_data_source.go b/internal/services/secret/secret_data_source.go index 74df8d13de..e87740de9d 100644 --- a/internal/services/secret/secret_data_source.go +++ b/internal/services/secret/secret_data_source.go @@ -79,6 +79,7 @@ func DataSourceSecretRead(ctx context.Context, d *schema.ResourceData, m interfa regionalID := datasource.NewRegionalID(secretID, region) d.SetId(regionalID) + err = d.Set("secret_id", regionalID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/secret/version_data_source.go b/internal/services/secret/version_data_source.go index fbcdc8f88a..cc0c5cd558 100644 --- a/internal/services/secret/version_data_source.go +++ b/internal/services/secret/version_data_source.go @@ -58,16 +58,19 @@ func DataSourceVersion() *schema.Resource { func datasourceSchemaFromResourceVersionSchema(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { secretID, existSecretID := d.GetOk("secret_id") + api, region, projectID, err := newAPIWithRegionOptionalProjectIDAndDefault(d, m, regional.ExpandID(secretID).Region) if err != nil { return diag.FromErr(err) } var secretVersionIDStr string + var payloadSecretRaw []byte if !existSecretID { secretName := d.Get("secret_name").(string) + secrets, err := api.ListSecrets(&secret.ListSecretsRequest{ Region: region, Name: &secretName, @@ -115,6 +118,7 @@ func datasourceSchemaFromResourceVersionSchema(ctx context.Context, d *schema.Re } d.SetId(secretVersionIDStr) + err = d.Set("data", base64.StdEncoding.EncodeToString(payloadSecretRaw)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/tem/domain_data_source.go b/internal/services/tem/domain_data_source.go index 363a774704..75d8dd497d 100644 --- a/internal/services/tem/domain_data_source.go +++ b/internal/services/tem/domain_data_source.go @@ -74,6 +74,7 @@ func DataSourceDomainRead(ctx context.Context, d *schema.ResourceData, m interfa regionalID := datasource.NewRegionalID(domainID, region) d.SetId(regionalID) + err = d.Set("domain_id", regionalID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/tem/domain_test.go b/internal/services/tem/domain_test.go index 8a53142985..4486fe9391 100644 --- a/internal/services/tem/domain_test.go +++ b/internal/services/tem/domain_test.go @@ -73,6 +73,7 @@ func TestAccDomain_Tos(t *testing.T) { func TestAccDomain_Autoconfig(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + subDomainName := "test-autoconfig" resource.ParallelTest(t, resource.TestCase{ @@ -119,6 +120,7 @@ func TestAccDomain_Autoconfig(t *testing.T) { func TestAccDomain_AutoconfigUpdate(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + subDomainName := "test-autoconfig-update" resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/tem/domain_validation.go b/internal/services/tem/domain_validation.go index eeabeeb795..19989db4f3 100644 --- a/internal/services/tem/domain_validation.go +++ b/internal/services/tem/domain_validation.go @@ -58,7 +58,9 @@ func ResourceDomainValidationCreate(ctx context.Context, d *schema.ResourceData, if err != nil { return diag.FromErr(err) } + d.SetId(d.Get("domain_id").(string)) + domain, err := api.GetDomain(&tem.GetDomainRequest{ Region: region, DomainID: extractAfterSlash(d.Get("domain_id").(string)), @@ -72,6 +74,7 @@ func ResourceDomainValidationCreate(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } + duration := d.Get("timeout").(int) timeout := time.Duration(duration) * time.Second _ = retry.RetryContext(ctx, timeout, func() *retry.RetryError { @@ -100,6 +103,7 @@ func ResourceDomainValidationRead(ctx context.Context, d *schema.ResourceData, m Region: region, DomainID: extractAfterSlash(domainID), } + domain, err := api.GetDomain(getDomainRequest, scw.WithContext(ctx)) if err != nil { if httperrors.Is404(err) { diff --git a/internal/services/tem/domain_validation_test.go b/internal/services/tem/domain_validation_test.go index ec4a64228b..8900daa6ff 100644 --- a/internal/services/tem/domain_validation_test.go +++ b/internal/services/tem/domain_validation_test.go @@ -13,6 +13,7 @@ const domainNameValidation = "scaleway-terraform.com" func TestAccDomainValidation_NoValidation(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + subDomainName := "validation-no-validation" resource.ParallelTest(t, resource.TestCase{ @@ -50,6 +51,7 @@ func TestAccDomainValidation_NoValidation(t *testing.T) { func TestAccDomainValidation_Validation(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + subDomainName := "validation-validation" resource.ParallelTest(t, resource.TestCase{ diff --git a/internal/services/tem/testfuncs/sweep.go b/internal/services/tem/testfuncs/sweep.go index 2dbf4f3ee6..d6f8fe14d2 100644 --- a/internal/services/tem/testfuncs/sweep.go +++ b/internal/services/tem/testfuncs/sweep.go @@ -20,6 +20,7 @@ func AddTestSweepers() { func testSweepDomain(_ string) error { return acctest.SweepRegions([]scw.Region{scw.RegionFrPar, scw.RegionNlAms}, func(scwClient *scw.Client, region scw.Region) error { temAPI := temSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: revoking the tem domains in (%s)", region) listDomains, err := temAPI.ListDomains(&temSDK.ListDomainsRequest{Region: region}, scw.WithAllPages()) @@ -33,6 +34,7 @@ func testSweepDomain(_ string) error { continue } + _, err := temAPI.RevokeDomain(&temSDK.RevokeDomainRequest{ DomainID: ns.ID, Region: region, diff --git a/internal/services/tem/webhook.go b/internal/services/tem/webhook.go index 564cfaa4a5..4150eb1d2c 100644 --- a/internal/services/tem/webhook.go +++ b/internal/services/tem/webhook.go @@ -145,9 +145,11 @@ func ResourceWebhookUpdate(ctx context.Context, d *schema.ResourceData, m interf if d.HasChange("event_types") { rawEventTypes := d.Get("event_types").([]interface{}) eventTypes := make([]tem.WebhookEventType, len(rawEventTypes)) + for i, raw := range rawEventTypes { eventTypes[i] = tem.WebhookEventType(raw.(string)) } + req.EventTypes = eventTypes } diff --git a/internal/services/tem/webhook_test.go b/internal/services/tem/webhook_test.go index cb2b7584e2..0b74e11fa5 100644 --- a/internal/services/tem/webhook_test.go +++ b/internal/services/tem/webhook_test.go @@ -197,6 +197,7 @@ func isWebhookDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { }, scw.WithContext(context.Background())) errorCode := httperrors.Is404(err) _ = errorCode + if err != nil && !httperrors.Is404(err) { return err } diff --git a/internal/services/vpc/helpers.go b/internal/services/vpc/helpers.go index 85ea979160..08e40f394a 100644 --- a/internal/services/vpc/helpers.go +++ b/internal/services/vpc/helpers.go @@ -72,6 +72,7 @@ func vpcPrivateNetworkV1SUpgradeFunc(_ context.Context, rawState map[string]inte if !exist { return nil, errors.New("upgrade: id not exist") } + rawState["id"], err = vpcPrivateNetworkUpgradeV1ZonalToRegionalID(ID.(string)) if err != nil { return nil, err @@ -131,6 +132,7 @@ func diffSuppressFuncRouteResourceID(_, oldValue, newValue string, _ *schema.Res if err != nil { return false } + newResourceID, err := vpcRouteExpandResourceID(newValue) if err != nil { return false diff --git a/internal/services/vpc/private_network_data_source.go b/internal/services/vpc/private_network_data_source.go index be98ef6909..f5f8977229 100644 --- a/internal/services/vpc/private_network_data_source.go +++ b/internal/services/vpc/private_network_data_source.go @@ -51,6 +51,7 @@ func DataSourceVPCPrivateNetworkRead(ctx context.Context, d *schema.ResourceData privateNetworkID, ok := d.GetOk("private_network_id") if !ok { pnName := d.Get("name").(string) + res, err := vpcAPI.ListPrivateNetworks( &vpc.ListPrivateNetworksRequest{ Name: types.ExpandStringPtr(pnName), @@ -77,6 +78,7 @@ func DataSourceVPCPrivateNetworkRead(ctx context.Context, d *schema.ResourceData regionalID := datasource.NewRegionalID(privateNetworkID, region) d.SetId(regionalID) _ = d.Set("private_network_id", regionalID) + diags := ResourceVPCPrivateNetworkRead(ctx, d, m) if diags != nil { return append(diags, diag.Errorf("failed to read private network state")...) diff --git a/internal/services/vpc/private_network_data_source_test.go b/internal/services/vpc/private_network_data_source_test.go index c09e9ec67a..c9a2c8523c 100644 --- a/internal/services/vpc/private_network_data_source_test.go +++ b/internal/services/vpc/private_network_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccDataSourceVPCPrivateNetwork_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + pnName := "TestAccScalewayDataSourceVPCPrivateNetwork_Basic" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/vpc/private_network_test.go b/internal/services/vpc/private_network_test.go index 01dbc7a8dc..cd0f2ec341 100644 --- a/internal/services/vpc/private_network_test.go +++ b/internal/services/vpc/private_network_test.go @@ -12,6 +12,7 @@ import ( func TestAccVPCPrivateNetwork_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + privateNetworkName := "private-network-test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/vpc/route.go b/internal/services/vpc/route.go index 325ced90bc..342ee1b494 100644 --- a/internal/services/vpc/route.go +++ b/internal/services/vpc/route.go @@ -146,6 +146,7 @@ func ResourceRouteRead(ctx context.Context, d *schema.ResourceData, m interface{ if err != nil { return diag.FromErr(err) } + _ = d.Set("destination", destination) if len(res.Tags) > 0 { diff --git a/internal/services/vpc/routes_data_source.go b/internal/services/vpc/routes_data_source.go index 244fc5d257..7d789eede9 100644 --- a/internal/services/vpc/routes_data_source.go +++ b/internal/services/vpc/routes_data_source.go @@ -149,6 +149,7 @@ func DataSourceRoutesRead(ctx context.Context, d *schema.ResourceData, m interfa } routes := []interface{}(nil) + for _, route := range res.Routes { rawRoute := make(map[string]interface{}) if route.Route != nil { @@ -164,12 +165,14 @@ func DataSourceRoutesRead(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } + rawRoute["destination"] = destination if len(route.Route.Tags) > 0 { rawRoute["tags"] = route.Route.Tags } } + rawRoute["nexthop_ip"] = types.FlattenIPPtr(route.NexthopIP) rawRoute["nexthop_name"] = types.FlattenStringPtr(route.NexthopName) rawRoute["nexthop_resource_type"] = route.NexthopResourceType.String() diff --git a/internal/services/vpc/testfuncs/checks.go b/internal/services/vpc/testfuncs/checks.go index 1268472f73..652040dffa 100644 --- a/internal/services/vpc/testfuncs/checks.go +++ b/internal/services/vpc/testfuncs/checks.go @@ -22,6 +22,7 @@ func CheckPrivateNetworkDestroy(tt *acctest.TestTools) resource.TestCheckFunc { if err != nil { return err } + _, err = vpcAPI.GetPrivateNetwork(&vpc2.GetPrivateNetworkRequest{ PrivateNetworkID: ID, Region: region, diff --git a/internal/services/vpc/testfuncs/sweep.go b/internal/services/vpc/testfuncs/sweep.go index 36a4fbcca6..5be9a87326 100644 --- a/internal/services/vpc/testfuncs/sweep.go +++ b/internal/services/vpc/testfuncs/sweep.go @@ -44,6 +44,7 @@ func testSweepVPC(_ string) error { if v.IsDefault { continue } + err := vpcAPI.DeleteVPC(&vpcSDK.DeleteVPCRequest{ VpcID: v.ID, Region: region, diff --git a/internal/services/vpc/types.go b/internal/services/vpc/types.go index 89918826fc..b2835f9836 100644 --- a/internal/services/vpc/types.go +++ b/internal/services/vpc/types.go @@ -15,10 +15,12 @@ func expandSubnets(d *schema.ResourceData) (ipv4Subnets []scw.IPNet, ipv6Subnets if v, ok := d.GetOk("ipv4_subnet"); ok { for _, s := range v.([]interface{}) { rawSubnet := s.(map[string]interface{}) + ipNet, err := types.ExpandIPNet(rawSubnet["subnet"].(string)) if err != nil { return nil, nil, err } + ipv4Subnets = append(ipv4Subnets, ipNet) } } @@ -26,10 +28,12 @@ func expandSubnets(d *schema.ResourceData) (ipv4Subnets []scw.IPNet, ipv6Subnets if v, ok := d.GetOk("ipv6_subnets"); ok { for _, s := range v.(*schema.Set).List() { rawSubnet := s.(map[string]interface{}) + ipNet, err := types.ExpandIPNet(rawSubnet["subnet"].(string)) if err != nil { return nil, nil, err } + ipv6Subnets = append(ipv6Subnets, ipNet) } } @@ -63,6 +67,7 @@ func flattenAndSortIPNetSubnets(subnets []scw.IPNet) (interface{}, interface{}) if err != nil { return "", nil } + flatIpv4Subnets = append(flatIpv4Subnets, map[string]interface{}{ "subnet": sub, "address": s.IP.String(), @@ -74,6 +79,7 @@ func flattenAndSortIPNetSubnets(subnets []scw.IPNet) (interface{}, interface{}) if err != nil { return "", nil } + flatIpv6Subnets = append(flatIpv6Subnets, map[string]interface{}{ "subnet": sub, "address": s.IP.String(), @@ -101,6 +107,7 @@ func flattenAndSortSubnetV2s(subnets []*vpc.Subnet) (interface{}, interface{}) { if err != nil { return "", nil } + flatIpv4Subnets = append(flatIpv4Subnets, map[string]interface{}{ "id": s.ID, "created_at": types.FlattenTime(s.CreatedAt), @@ -115,6 +122,7 @@ func flattenAndSortSubnetV2s(subnets []*vpc.Subnet) (interface{}, interface{}) { if err != nil { return "", nil } + flatIpv6Subnets = append(flatIpv6Subnets, map[string]interface{}{ "id": s.ID, "created_at": types.FlattenTime(s.CreatedAt), diff --git a/internal/services/vpc/vpc_data_source.go b/internal/services/vpc/vpc_data_source.go index 28967209f6..25249b1659 100644 --- a/internal/services/vpc/vpc_data_source.go +++ b/internal/services/vpc/vpc_data_source.go @@ -48,6 +48,7 @@ func DataSourceVPCRead(ctx context.Context, d *schema.ResourceData, m interface{ } var vpcID interface{} + var ok bool if d.Get("is_default").(bool) { @@ -94,6 +95,7 @@ func DataSourceVPCRead(ctx context.Context, d *schema.ResourceData, m interface{ regionalID := datasource.NewRegionalID(vpcID, region) d.SetId(regionalID) + err = d.Set("vpc_id", regionalID) if err != nil { return diag.FromErr(err) diff --git a/internal/services/vpc/vpc_data_source_test.go b/internal/services/vpc/vpc_data_source_test.go index f8f73346e7..fdb2b58fe9 100644 --- a/internal/services/vpc/vpc_data_source_test.go +++ b/internal/services/vpc/vpc_data_source_test.go @@ -11,6 +11,7 @@ import ( func TestAccDataSourceVPC_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + vpcName := "DataSourceVPC_Basic" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/vpc/vpcs_data_source.go b/internal/services/vpc/vpcs_data_source.go index 67c4b1ac5c..a886ec42f3 100644 --- a/internal/services/vpc/vpcs_data_source.go +++ b/internal/services/vpc/vpcs_data_source.go @@ -91,6 +91,7 @@ func DataSourceVPCsRead(ctx context.Context, d *schema.ResourceData, m interface } vpcs := []interface{}(nil) + for _, virtualPrivateCloud := range res.Vpcs { rawVpc := make(map[string]interface{}) rawVpc["id"] = regional.NewIDString(region, virtualPrivateCloud.ID) @@ -98,9 +99,11 @@ func DataSourceVPCsRead(ctx context.Context, d *schema.ResourceData, m interface rawVpc["created_at"] = types.FlattenTime(virtualPrivateCloud.CreatedAt) rawVpc["update_at"] = types.FlattenTime(virtualPrivateCloud.UpdatedAt) rawVpc["is_default"] = virtualPrivateCloud.IsDefault + if len(virtualPrivateCloud.Tags) > 0 { rawVpc["tags"] = virtualPrivateCloud.Tags } + rawVpc["region"] = region.String() rawVpc["organization_id"] = virtualPrivateCloud.OrganizationID rawVpc["project_id"] = virtualPrivateCloud.ProjectID diff --git a/internal/services/vpcgw/dhcp.go b/internal/services/vpcgw/dhcp.go index 72834de7a6..437d5d4b34 100644 --- a/internal/services/vpcgw/dhcp.go +++ b/internal/services/vpcgw/dhcp.go @@ -140,6 +140,7 @@ func ResourceVPCPublicGatewayDHCPCreate(ctx context.Context, d *schema.ResourceD if err != nil { return diag.FromErr(err) } + req := &vpcgw.CreateDHCPRequest{ Zone: zone, ProjectID: d.Get("project_id").(string), @@ -254,6 +255,7 @@ func ResourceVPCPublicGatewayDHCPUpdate(ctx context.Context, d *schema.ResourceD if err != nil { return diag.FromErr(err) } + req.Subnet = &subnet } diff --git a/internal/services/vpcgw/dhcp_reservation.go b/internal/services/vpcgw/dhcp_reservation.go index d0d51ee254..0d99e301d4 100644 --- a/internal/services/vpcgw/dhcp_reservation.go +++ b/internal/services/vpcgw/dhcp_reservation.go @@ -95,6 +95,7 @@ func ResourceVPCPublicGatewayDHCPCReservationCreate(ctx context.Context, d *sche } gatewayNetworkID := locality.ExpandID(d.Get("gateway_network_id")) + _, err = waitForVPCGatewayNetwork(ctx, api, zone, gatewayNetworkID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -165,6 +166,7 @@ func ResourceVPCPublicGatewayDHCPReservationUpdate(ctx context.Context, d *schem } gatewayNetworkID := locality.ExpandID(d.Get("gateway_network_id")) + _, err = waitForVPCGatewayNetwork(ctx, api, zone, gatewayNetworkID, d.Timeout(schema.TimeoutUpdate)) if err != nil { return diag.FromErr(err) @@ -197,6 +199,7 @@ func ResourceVPCPublicGatewayDHCPReservationDelete(ctx context.Context, d *schem } gatewayNetworkID := locality.ExpandID(d.Get("gateway_network_id")) + _, err = waitForVPCGatewayNetwork(ctx, api, zone, gatewayNetworkID, d.Timeout(schema.TimeoutDelete)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/vpcgw/dhcp_reservation_data_source.go b/internal/services/vpcgw/dhcp_reservation_data_source.go index 2931ae27f5..bf8a8d1fa1 100644 --- a/internal/services/vpcgw/dhcp_reservation_data_source.go +++ b/internal/services/vpcgw/dhcp_reservation_data_source.go @@ -55,6 +55,7 @@ func DataSourceDHCPReservationRead(ctx context.Context, d *schema.ResourceData, reservationIDRaw, ok := d.GetOk("reservation_id") if !ok { var res *vpcgw.ListDHCPEntriesResponse + gatewayNetworkID := locality.ExpandID(d.Get("gateway_network_id").(string)) macAddress := d.Get("mac_address").(string) @@ -67,6 +68,7 @@ func DataSourceDHCPReservationRead(ctx context.Context, d *schema.ResourceData, MacAddress: types.ExpandStringPtr(macAddress), }, scw.WithContext(ctx)) } + if err != nil { return diag.FromErr(err) } @@ -79,6 +81,7 @@ func DataSourceDHCPReservationRead(ctx context.Context, d *schema.ResourceData, ), ) } + if res.TotalCount > 1 { return diag.FromErr( fmt.Errorf( @@ -88,6 +91,7 @@ func DataSourceDHCPReservationRead(ctx context.Context, d *schema.ResourceData, ), ) } + reservationIDRaw = res.DHCPEntries[0].ID } diff --git a/internal/services/vpcgw/dhcp_reservation_data_source_test.go b/internal/services/vpcgw/dhcp_reservation_data_source_test.go index bb16dea16c..650e65eb75 100644 --- a/internal/services/vpcgw/dhcp_reservation_data_source_test.go +++ b/internal/services/vpcgw/dhcp_reservation_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccDataSourceVPCPublicGatewayDHCPReservation_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + pnName := "TestAccScalewayDataSourceVPCPublicGatewayDHCPReservation_Basic" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -116,6 +117,7 @@ func TestAccDataSourceVPCPublicGatewayDHCPReservation_Basic(t *testing.T) { func TestAccDataSourceVPCPublicGatewayDHCPReservation_Static(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + pnName := "TestAccScalewayDataSourceVPCPublicGatewayDHCPReservation_Static" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/vpcgw/helpers.go b/internal/services/vpcgw/helpers.go index 7c0be57c04..4f1a338c90 100644 --- a/internal/services/vpcgw/helpers.go +++ b/internal/services/vpcgw/helpers.go @@ -66,6 +66,7 @@ func expandIpamConfig(raw interface{}) *vpcgw.CreateGatewayNetworkRequestIpamCon if raw == nil || len(raw.([]interface{})) != 1 { return nil } + rawMap := raw.([]interface{})[0].(map[string]interface{}) ipamConfig := &vpcgw.CreateGatewayNetworkRequestIpamConfig{ @@ -83,6 +84,7 @@ func expandUpdateIpamConfig(raw interface{}) *vpcgw.UpdateGatewayNetworkRequestI if raw == nil || len(raw.([]interface{})) != 1 { return nil } + rawMap := raw.([]interface{})[0].(map[string]interface{}) updateIpamConfig := &vpcgw.UpdateGatewayNetworkRequestIpamConfig{ diff --git a/internal/services/vpcgw/ip.go b/internal/services/vpcgw/ip.go index 287693628b..55994291ce 100644 --- a/internal/services/vpcgw/ip.go +++ b/internal/services/vpcgw/ip.go @@ -89,6 +89,7 @@ func ResourceIPCreate(ctx context.Context, d *schema.ResourceData, m interface{} Tags: scw.StringsPtr(types.ExpandStrings(d.Get("tags"))), Reverse: types.ExpandStringPtr(reverse.(string)), } + _, err = api.UpdateIP(updateRequest, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) @@ -170,6 +171,7 @@ func ResourceVPCPublicGatewayIPDelete(ctx context.Context, d *schema.ResourceDat } var warnings diag.Diagnostics + err = api.DeleteIP(&vpcgw.DeleteIPRequest{ IPID: ID, Zone: zone, diff --git a/internal/services/vpcgw/ip_reverse.go b/internal/services/vpcgw/ip_reverse.go index 2af8216fa3..86c81e5d02 100644 --- a/internal/services/vpcgw/ip_reverse.go +++ b/internal/services/vpcgw/ip_reverse.go @@ -59,6 +59,7 @@ func ResourceVPCPublicGatewayIPReverseDNSCreate(ctx context.Context, d *schema.R if err != nil { return diag.FromErr(err) } + d.SetId(zonal.NewIDString(zone, res.ID)) if _, ok := d.GetOk("reverse"); ok { @@ -149,6 +150,7 @@ func ResourceVPCPublicGatewayIPReverseDNSDelete(ctx context.Context, d *schema.R IPID: ID, Reverse: new(string), } + _, err = api.UpdateIP(updateReverseReq, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/vpcgw/ip_reverse_test.go b/internal/services/vpcgw/ip_reverse_test.go index 1536205097..1b99879a17 100644 --- a/internal/services/vpcgw/ip_reverse_test.go +++ b/internal/services/vpcgw/ip_reverse_test.go @@ -16,6 +16,7 @@ import ( func TestAccVPCPublicGatewayIPReverseDns_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + testDNSZone := "tf-reverse-vpcgw." + acctest.TestDomain resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/vpcgw/network.go b/internal/services/vpcgw/network.go index b8f253b92a..5c0e6c774a 100644 --- a/internal/services/vpcgw/network.go +++ b/internal/services/vpcgw/network.go @@ -158,12 +158,14 @@ func ResourceVPCGatewayNetworkCreate(ctx context.Context, d *schema.ResourceData EnableDHCP: types.ExpandBoolPtr(d.Get("enable_dhcp")), IpamConfig: expandIpamConfig(d.Get("ipam_config")), } + staticAddress, staticAddressExist := d.GetOk("static_address") if staticAddressExist { address, err := types.ExpandIPNet(staticAddress.(string)) if err != nil { return diag.FromErr(err) } + req.Address = &address } @@ -215,6 +217,7 @@ func ResourceVPCGatewayNetworkRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } + _, err = waitForVPCPublicGateway(ctx, api, zone, gatewayNetwork.GatewayID, d.Timeout(schema.TimeoutRead)) if err != nil { return diag.FromErr(err) @@ -229,6 +232,7 @@ func ResourceVPCGatewayNetworkRead(ctx context.Context, d *schema.ResourceData, if err != nil { return diag.FromErr(err) } + _ = d.Set("static_address", staticAddressValue) } @@ -245,6 +249,7 @@ func ResourceVPCGatewayNetworkRead(ctx context.Context, d *schema.ResourceData, } var cleanUpDHCPValue bool + cleanUpDHCP, cleanUpDHCPExist := d.GetOk("cleanup_dhcp") if cleanUpDHCPExist { cleanUpDHCPValue = *types.ExpandBoolPtr(cleanUpDHCP) @@ -291,16 +296,20 @@ func ResourceVPCGatewayNetworkUpdate(ctx context.Context, d *schema.ResourceData if d.HasChange("enable_masquerade") { updateRequest.EnableMasquerade = types.ExpandBoolPtr(d.Get("enable_masquerade")) } + if d.HasChange("enable_dhcp") { updateRequest.EnableDHCP = types.ExpandBoolPtr(d.Get("enable_dhcp")) } + if d.HasChange("dhcp_id") { dhcpID := zonal.ExpandID(d.Get("dhcp_id").(string)).ID updateRequest.DHCPID = &dhcpID } + if d.HasChange("ipam_config") { updateRequest.IpamConfig = expandUpdateIpamConfig(d.Get("ipam_config")) } + if d.HasChange("static_address") { staticAddress, staticAddressExist := d.GetOk("static_address") if staticAddressExist { @@ -308,6 +317,7 @@ func ResourceVPCGatewayNetworkUpdate(ctx context.Context, d *schema.ResourceData if err != nil { return diag.FromErr(err) } + updateRequest.Address = &address } } @@ -341,6 +351,7 @@ func ResourceVPCGatewayNetworkDelete(ctx context.Context, d *schema.ResourceData Zone: gwNetwork.Zone, CleanupDHCP: *types.ExpandBoolPtr(d.Get("cleanup_dhcp")), } + err = api.DeleteGatewayNetwork(req, scw.WithContext(ctx)) if err != nil { return diag.FromErr(err) diff --git a/internal/services/vpcgw/network_data_source.go b/internal/services/vpcgw/network_data_source.go index 1e0be6b0a2..9812d1e68f 100644 --- a/internal/services/vpcgw/network_data_source.go +++ b/internal/services/vpcgw/network_data_source.go @@ -60,12 +60,15 @@ func DataSourceVPCGatewayNetworkRead(ctx context.Context, d *schema.ResourceData if err != nil { return diag.FromErr(err) } + if res.TotalCount == 0 { return diag.FromErr(errors.New("no gateway network found with the filters")) } + if res.TotalCount > 1 { return diag.FromErr(fmt.Errorf("%d gateway networks found with filters", res.TotalCount)) } + gatewayNetworkID = res.GatewayNetworks[0].ID } diff --git a/internal/services/vpcgw/pat_rule.go b/internal/services/vpcgw/pat_rule.go index 7f5bfb3b35..b212f6a331 100644 --- a/internal/services/vpcgw/pat_rule.go +++ b/internal/services/vpcgw/pat_rule.go @@ -94,6 +94,7 @@ func ResourceVPCPublicGatewayPATRuleCreate(ctx context.Context, d *schema.Resour } gatewayID := zonal.ExpandID(d.Get("gateway_id").(string)).ID + _, err = waitForVPCPublicGateway(ctx, api, zone, gatewayID, d.Timeout(schema.TimeoutCreate)) if err != nil { return diag.FromErr(err) @@ -188,6 +189,7 @@ func ResourceVPCPublicGatewayPATRuleUpdate(ctx context.Context, d *schema.Resour } hasChange := false + if d.HasChange("private_ip") { req.PrivateIP = scw.IPPtr(net.ParseIP(d.Get("private_ip").(string))) hasChange = true diff --git a/internal/services/vpcgw/public_gateway_data_source.go b/internal/services/vpcgw/public_gateway_data_source.go index 8d09cc216a..7517ae4c56 100644 --- a/internal/services/vpcgw/public_gateway_data_source.go +++ b/internal/services/vpcgw/public_gateway_data_source.go @@ -47,6 +47,7 @@ func DataSourceVPCPublicGatewayRead(ctx context.Context, d *schema.ResourceData, publicGatewayID, ok := d.GetOk("public_gateway_id") if !ok { gwName := d.Get("name").(string) + res, err := api.ListGateways( &vpcgw.ListGatewaysRequest{ Name: types.ExpandStringPtr(gwName), diff --git a/internal/services/vpcgw/public_gateway_data_source_test.go b/internal/services/vpcgw/public_gateway_data_source_test.go index 60cd886358..92c25b20a4 100644 --- a/internal/services/vpcgw/public_gateway_data_source_test.go +++ b/internal/services/vpcgw/public_gateway_data_source_test.go @@ -12,6 +12,7 @@ import ( func TestAccDataSourceVPCPublicGateway_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + pgName := "TestAccScalewayDataSourceVPCPublicGateway_Basic" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/vpcgw/public_gateway_test.go b/internal/services/vpcgw/public_gateway_test.go index 25d286df0d..66305979c9 100644 --- a/internal/services/vpcgw/public_gateway_test.go +++ b/internal/services/vpcgw/public_gateway_test.go @@ -15,6 +15,7 @@ import ( func TestAccVPCPublicGateway_Basic(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + publicGatewayName := "public-gateway-test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -94,6 +95,7 @@ func TestAccVPCPublicGateway_Basic(t *testing.T) { func TestAccVPCPublicGateway_Bastion(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + publicGatewayName := "public-gateway-bastion-test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -177,6 +179,7 @@ func TestAccVPCPublicGateway_AttachToIP(t *testing.T) { func TestAccVPCPublicGateway_Upgrade(t *testing.T) { tt := acctest.NewTestTools(t) defer tt.Cleanup() + publicGatewayName := "public-gateway-upgrade-test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/services/vpcgw/testfuncs/sweep.go b/internal/services/vpcgw/testfuncs/sweep.go index 53ec49dd8d..3c41cbcb08 100644 --- a/internal/services/vpcgw/testfuncs/sweep.go +++ b/internal/services/vpcgw/testfuncs/sweep.go @@ -32,6 +32,7 @@ func AddTestSweepers() { func testSweepVPCPublicGateway(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { api := vpcgwSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the public gateways in (%+v)", zone) listGatewayResponse, err := api.ListGateways(&vpcgwSDK.ListGatewaysRequest{ @@ -58,6 +59,7 @@ func testSweepVPCPublicGateway(_ string) error { func testSweepVPCGatewayNetwork(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { api := vpcgwSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the gateway network in (%s)", zone) listPNResponse, err := api.ListGatewayNetworks(&vpcgwSDK.ListGatewayNetworksRequest{ @@ -86,6 +88,7 @@ func testSweepVPCGatewayNetwork(_ string) error { func testSweepVPCPublicGatewayIP(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { api := vpcgwSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying the public gateways ip in (%s)", zone) listIPResponse, err := api.ListIPs(&vpcgwSDK.ListIPsRequest{ @@ -112,6 +115,7 @@ func testSweepVPCPublicGatewayIP(_ string) error { func testSweepVPCPublicGatewayDHCP(_ string) error { return acctest.SweepZones(scw.AllZones, func(scwClient *scw.Client, zone scw.Zone) error { api := vpcgwSDK.NewAPI(scwClient) + logging.L.Debugf("sweeper: destroying public gateway dhcps in (%+v)", zone) listDHCPsResponse, err := api.ListDHCPs(&vpcgwSDK.ListDHCPsRequest{ diff --git a/internal/services/webhosting/offer_data_source.go b/internal/services/webhosting/offer_data_source.go index 6f7e8f38fd..fe860becd7 100644 --- a/internal/services/webhosting/offer_data_source.go +++ b/internal/services/webhosting/offer_data_source.go @@ -97,16 +97,19 @@ func dataSourceOfferRead(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } + if len(res.Offers) == 0 { return diag.FromErr(fmt.Errorf("no offer found in region %s", region)) } var filteredOffer *webhosting.Offer + for _, offer := range res.Offers { if offer.ID == d.Get("offer_id") || offer.Product.Name == d.Get("name") { filteredOffer = offer } } + if filteredOffer == nil { return diag.FromErr(fmt.Errorf("no offer found with the name or id: %s%s in region %s", d.Get("name"), d.Get("offer_id"), region)) } diff --git a/internal/services/webhosting/types.go b/internal/services/webhosting/types.go index 63c858c15b..5391134721 100644 --- a/internal/services/webhosting/types.go +++ b/internal/services/webhosting/types.go @@ -38,6 +38,7 @@ func flattenHostingOptions(options []*webhosting.HostingOption) []map[string]int if options == nil { return nil } + flattenedOptions := []map[string]interface{}(nil) for _, option := range options { flattenedOptions = append(flattenedOptions, map[string]interface{}{ diff --git a/internal/services/webhosting/webhosting.go b/internal/services/webhosting/webhosting.go index 10058ca711..c29c931d9e 100644 --- a/internal/services/webhosting/webhosting.go +++ b/internal/services/webhosting/webhosting.go @@ -276,6 +276,7 @@ func resourceWebhostingUpdate(ctx context.Context, d *schema.ResourceData, m int if err != nil { return diag.FromErr(err) } + updateRequest.OfferID = types.ExpandUpdatedStringPtr(offerID) hasChanged = true } diff --git a/internal/services/webhosting/webhosting_data_source.go b/internal/services/webhosting/webhosting_data_source.go index 52808ca7c7..15e3763f18 100644 --- a/internal/services/webhosting/webhosting_data_source.go +++ b/internal/services/webhosting/webhosting_data_source.go @@ -49,6 +49,7 @@ func DataSourceWebhostingRead(ctx context.Context, d *schema.ResourceData, m int webhostingID, ok := d.GetOk("webhosting_id") if !ok { hostingDomain := d.Get("domain").(string) + res, err := api.ListHostings(&webhosting.ListHostingsRequest{ Region: region, Domain: types.ExpandStringPtr(hostingDomain), @@ -73,6 +74,7 @@ func DataSourceWebhostingRead(ctx context.Context, d *schema.ResourceData, m int regionalID := datasource.NewRegionalID(webhostingID, region) d.SetId(regionalID) + err = d.Set("webhosting_id", regionalID) if err != nil { return diag.FromErr(err) diff --git a/internal/transport/retry.go b/internal/transport/retry.go index 76704e3545..38a313a51b 100644 --- a/internal/transport/retry.go +++ b/internal/transport/retry.go @@ -54,9 +54,11 @@ func NewRetryableTransportWithOptions(defaultTransport http.RoundTripper, option if options.RetryMax != nil { c.RetryMax = *options.RetryMax } + if options.RetryWaitMax != nil { c.RetryWaitMax = *options.RetryWaitMax } + if options.RetryWaitMin != nil { c.RetryWaitMin = *options.RetryWaitMin } @@ -78,20 +80,25 @@ type RetryableTransport struct { // RoundTrip wraps calling an HTTP method with retries. func (c *RetryableTransport) RoundTrip(r *http.Request) (*http.Response, error) { var body io.ReadSeeker + if r.Body != nil { bs, err := io.ReadAll(r.Body) if err != nil { return nil, err } + body = bytes.NewReader(bs) } + req, err := retryablehttp.NewRequest(r.Method, r.URL.String(), body) if err != nil { return nil, err } + for key, val := range r.Header { req.Header.Set(key, val[0]) } + req.GetBody = func() (io.ReadCloser, error) { b, err := req.BodyBytes() if err != nil { @@ -106,7 +113,9 @@ func (c *RetryableTransport) RoundTrip(r *http.Request) (*http.Response, error) func RetryOnTransientStateError[T any, U any](action func() (T, error), waiter func() (U, error)) (T, error) { //nolint:ireturn t, err := action() + var transientStateError *scw.TransientStateError + if errors.As(err, &transientStateError) { _, err := waiter() if err != nil { diff --git a/internal/types/ip.go b/internal/types/ip.go index 7c03df6714..f7af5726eb 100644 --- a/internal/types/ip.go +++ b/internal/types/ip.go @@ -16,7 +16,9 @@ func ExpandIPNet(raw string) (scw.IPNet, error) { if raw == "" { return scw.IPNet{}, nil } + var ipNet scw.IPNet + err := json.Unmarshal([]byte(strconv.Quote(raw)), &ipNet) if err != nil { return scw.IPNet{}, fmt.Errorf("%s could not be marshaled: %v", raw, err) diff --git a/internal/types/map.go b/internal/types/map.go index b747f86608..9dcbe8b6f9 100644 --- a/internal/types/map.go +++ b/internal/types/map.go @@ -4,6 +4,7 @@ func FlattenMap(m map[string]string) interface{} { if m == nil { return nil } + flattenedMap := make(map[string]interface{}) for k, v := range m { flattenedMap[k] = v @@ -16,7 +17,9 @@ func FlattenMapStringStringPtr(m map[string]*string) interface{} { if m == nil { return nil } + flattenedMap := make(map[string]interface{}) + for k, v := range m { if v != nil { flattenedMap[k] = *v @@ -32,6 +35,7 @@ func ExpandMapPtrStringString(data interface{}) *map[string]string { if data == nil { return nil } + m := make(map[string]string) for k, v := range data.(map[string]interface{}) { m[k] = v.(string) @@ -44,6 +48,7 @@ func ExpandMapStringStringPtr(data interface{}) map[string]*string { if data == nil { return nil } + m := make(map[string]*string) for k, v := range data.(map[string]interface{}) { m[k] = ExpandStringPtr(v) @@ -56,6 +61,7 @@ func ExpandMapStringString(data any) map[string]string { if data == nil { return nil } + m := make(map[string]string) for k, v := range data.(map[string]interface{}) { m[k] = v.(string) @@ -71,6 +77,7 @@ func GetMapValue[T any]( //nolint:ireturn key string, ) T { var val T + valI, exists := m[key] if exists { val = valI.(T) diff --git a/internal/types/string.go b/internal/types/string.go index e49f3af0f7..b48360fa4a 100644 --- a/internal/types/string.go +++ b/internal/types/string.go @@ -52,6 +52,7 @@ func ExpandSliceStringPtr(data interface{}) []*string { if data == nil { return nil } + stringSlice := []*string(nil) for _, s := range data.([]interface{}) { stringSlice = append(stringSlice, ExpandStringPtr(s)) @@ -89,11 +90,13 @@ func ExpandUpdatedStringPtr(data interface{}) *string { func ExpandStrings(data interface{}) []string { stringSlice := make([]string, 0, len(data.([]interface{}))) + for _, s := range data.([]interface{}) { // zero-value is nil, ["foo", ""] if s == nil { s = "" } + stringSlice = append(stringSlice, s.(string)) } @@ -102,16 +105,20 @@ func ExpandStrings(data interface{}) []string { func ExpandStringsPtr(data interface{}) *[]string { stringSlice := make([]string, 0, len(data.([]interface{}))) + if _, ok := data.([]interface{}); !ok || data == nil { return nil } + for _, s := range data.([]interface{}) { // zero-value is nil, ["foo", ""] if s == nil { s = "" } + stringSlice = append(stringSlice, s.(string)) } + if len(stringSlice) == 0 { return nil } @@ -126,11 +133,13 @@ func ExpandUpdatedStringsPtr(data interface{}) *[]string { if _, ok := data.([]interface{}); !ok || data == nil { return &stringSlice } + for _, s := range data.([]interface{}) { // zero-value is nil, ["foo", ""] if s == nil { s = "" } + stringSlice = append(stringSlice, s.(string)) } @@ -142,6 +151,7 @@ func ExpandSliceIDs(rawIDs interface{}) []string { if _, ok := rawIDs.([]interface{}); !ok || rawIDs == nil { return stringSlice } + for _, s := range rawIDs.([]interface{}) { stringSlice = append(stringSlice, locality.ExpandID(s.(string))) } @@ -154,6 +164,7 @@ func ExpandSliceIDsPtr(rawIDs interface{}) *[]string { if _, ok := rawIDs.([]interface{}); !ok || rawIDs == nil { return &stringSlice } + for _, s := range rawIDs.([]interface{}) { stringSlice = append(stringSlice, locality.ExpandID(s.(string))) } @@ -166,6 +177,7 @@ func ExpandStringsOrEmpty(data interface{}) []string { if _, ok := data.([]interface{}); !ok || data == nil { return stringSlice } + for _, s := range data.([]interface{}) { stringSlice = append(stringSlice, s.(string)) } @@ -192,6 +204,7 @@ func StringHashcode(s string) int { if v >= 0 { return v } + if -v >= 0 { return -v } diff --git a/internal/types/string_test.go b/internal/types/string_test.go index c8b74c0623..d9a51136bc 100644 --- a/internal/types/string_test.go +++ b/internal/types/string_test.go @@ -26,6 +26,7 @@ func TestStringHashcode_positiveIndex(t *testing.T) { func TestStringHashcode(t *testing.T) { v := "hello, world" expected := types.StringHashcode(v) + for range 100 { actual := types.StringHashcode(v) if actual != expected { diff --git a/internal/types/time.go b/internal/types/time.go index 7d47b4c9fe..8cea53d913 100644 --- a/internal/types/time.go +++ b/internal/types/time.go @@ -14,6 +14,7 @@ func ExpandDuration(data interface{}) (*time.Duration, error) { if data == nil || data == "" { return nil, nil } + d, err := time.ParseDuration(data.(string)) if err != nil { return nil, err @@ -37,6 +38,7 @@ func ExpandTimePtr(i interface{}) *time.Time { if rawTime == nil { return nil } + parsedTime, err := time.Parse(time.RFC3339, *rawTime) if err != nil { return nil diff --git a/internal/verify/date.go b/internal/verify/date.go index 5234a5f828..9443eb7e8a 100644 --- a/internal/verify/date.go +++ b/internal/verify/date.go @@ -20,6 +20,7 @@ func IsDate() schema.SchemaValidateDiagFunc { Summary: "invalid input, expected a string", }} } + _, err := time.Parse(time.RFC3339, date) if err != nil { return diag.Diagnostics{diag.Diagnostic{ diff --git a/internal/verify/enum.go b/internal/verify/enum.go index 463168a9d5..b8ab03b0dd 100644 --- a/internal/verify/enum.go +++ b/internal/verify/enum.go @@ -30,6 +30,7 @@ func getValues[T EnumValues[T]]() []string { var t T values := t.Values() result := make([]string, len(values)) + for i, v := range values { result[i] = string(v) } @@ -40,10 +41,12 @@ func getValues[T EnumValues[T]]() []string { // filterUnknownValues removes "unknown" and "unknown_*" values from the slice func filterUnknownValues(values []string) []string { filtered := make([]string, 0, len(values)) + for _, v := range values { if v == "unknown" || strings.HasPrefix(v, "unknown_") { continue } + filtered = append(filtered, v) } diff --git a/internal/workerpool/workerpool_test.go b/internal/workerpool/workerpool_test.go index 9460c75846..00169ac477 100644 --- a/internal/workerpool/workerpool_test.go +++ b/internal/workerpool/workerpool_test.go @@ -83,6 +83,7 @@ func TestWorkerPoolWaitTimeMultiple(t *testing.T) { for i := range iterations { if i%2 == 0 { found := false + for _, err := range errs { if err.Error() == fmt.Sprintf("error %d", i) { found = true diff --git a/main.go b/main.go index 412128fe3a..108588da61 100644 --- a/main.go +++ b/main.go @@ -15,8 +15,10 @@ func main() { ctx := context.Background() var debugMode bool + flag.BoolVar(&debugMode, "debuggable", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() + var serveOpts []tf5server.ServeOpt if debugMode { serveOpts = append(serveOpts, tf5server.WithManagedDebug())