Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions internal/acctest/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,35 @@ import (
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
)

var (
ErrResourceIDNotSet = errors.New("resourceID was not set")
ErrResourceNotFound = errors.New("resource was not found")
ErrResourceIDPersisted = errors.New("resource ID persisted when it should have changed")
ErrResourceIDChanged = errors.New("resource ID changed when it should have persisted")
ErrResourceAttributeNotFound = errors.New("resource attribute not found")
ErrResourceNotFoundSimple = errors.New("not found")
ErrIDMismatch = errors.New("ID mismatch")
ErrResourceNotDestroyed = errors.New("resource was not destroyed")
ErrInvalidIPv4 = errors.New("is not a valid IPv4")
ErrInvalidIPv6 = errors.New("is not a valid IPv6")
ErrInvalidIP = errors.New("is not a valid IP")
)

// CheckResourceIDChanged checks that the ID of the resource has indeed changed, in case of ForceNew for example.
// It will fail if resourceID is empty so be sure to use acctest.CheckResourceIDPersisted first in a test suite.
func CheckResourceIDChanged(resourceName string, resourceID *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if resourceID == nil || *resourceID == "" {
return errors.New("resourceID was not set")
return ErrResourceIDNotSet
}

rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource was not found: %s", resourceName)
return fmt.Errorf("%w: %s", ErrResourceNotFound, resourceName)
}

if *resourceID == rs.Primary.ID {
return errors.New("resource ID persisted when it should have changed")
return ErrResourceIDPersisted
}

*resourceID = rs.Primary.ID
Expand All @@ -40,11 +54,11 @@ func CheckResourceIDPersisted(resourceName string, resourceID *string) resource.
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource was not found: %s", resourceName)
return fmt.Errorf("%w: %s", ErrResourceNotFound, resourceName)
}

if *resourceID != "" && *resourceID != rs.Primary.ID {
return errors.New("resource ID changed when it should have persisted")
return ErrResourceIDChanged
}

*resourceID = rs.Primary.ID
Expand All @@ -58,19 +72,19 @@ func CheckResourceRawIDMatches(res1, attr1, res2, attr2 string) resource.TestChe
return func(s *terraform.State) error {
rs1, ok1 := s.RootModule().Resources[res1]
if !ok1 {
return fmt.Errorf("not found: %s", res1)
return fmt.Errorf("%w: %s", ErrResourceNotFoundSimple, res1)
}

rs2, ok2 := s.RootModule().Resources[res2]
if !ok2 {
return fmt.Errorf("not found: %s", res2)
return fmt.Errorf("%w: %s", ErrResourceNotFoundSimple, res2)
}

id1 := locality.ExpandID(rs1.Primary.Attributes[attr1])
id2 := locality.ExpandID(rs2.Primary.Attributes[attr2])

if id1 != id2 {
return fmt.Errorf("ID mismatch: %s from resource %s does not match ID %s from resource %s", id1, res1, id2, res2)
return fmt.Errorf("%w: %s from resource %s does not match ID %s from resource %s", ErrIDMismatch, id1, res1, id2, res2)
}

return nil
Expand All @@ -87,12 +101,12 @@ func CheckResourceAttrFunc(name string, key string, test func(string) error) res
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("resource not found: %s", name)
return fmt.Errorf("%w: %s", ErrResourceNotFoundSimple, name)
}

value, ok := rs.Primary.Attributes[key]
if !ok {
return fmt.Errorf("key not found: %s", key)
return fmt.Errorf("%w: %s", ErrResourceAttributeNotFound, key)
}

err := test(value)
Expand All @@ -108,7 +122,7 @@ func CheckResourceAttrIPv4(name string, key string) resource.TestCheckFunc {
return CheckResourceAttrFunc(name, key, func(value string) error {
ip := net.ParseIP(value)
if ip.To4() == nil {
return fmt.Errorf("%s is not a valid IPv4", value)
return fmt.Errorf("%w", ErrInvalidIPv4)
}

return nil
Expand All @@ -119,7 +133,7 @@ func CheckResourceAttrIPv6(name string, key string) resource.TestCheckFunc {
return CheckResourceAttrFunc(name, key, func(value string) error {
ip := net.ParseIP(value)
if ip.To16() == nil {
return fmt.Errorf("%s is not a valid IPv6", value)
return fmt.Errorf("%w", ErrInvalidIPv6)
}

return nil
Expand All @@ -130,7 +144,7 @@ func CheckResourceAttrIP(name string, key string) resource.TestCheckFunc {
return CheckResourceAttrFunc(name, key, func(value string) error {
ip := net.ParseIP(value)
if ip == nil {
return fmt.Errorf("%s is not a valid IP", value)
return fmt.Errorf("%w", ErrInvalidIP)
}

return nil
Expand Down
9 changes: 7 additions & 2 deletions internal/acctest/validate_cassettes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import (
"gopkg.in/dnaeon/go-vcr.v3/cassette"
)

var (
ErrCassetteStatusMismatch = errors.New("status mismatch found in cassette")
ErrNoMatchingTest = errors.New("cassette has no matching test")
)

const servicesDir = "../services"

func exceptionsCassettesCases() map[string]struct{} {
Expand Down Expand Up @@ -82,7 +87,7 @@ func checkErrorCode(c *cassette.Cassette) error {
for _, i := range c.Interactions {
if !checkErrCodeExcept(i, c, http.StatusBadRequest, http.StatusNotFound, http.StatusTooManyRequests, http.StatusForbidden, http.StatusGone) &&
!isTransientStateError(i) {
return fmt.Errorf("status: %v found on %s. method: %s, url %s\nrequest body = %v\nresponse body = %v", i.Response.Code, c.Name, i.Request.Method, i.Request.URL, i.Request.Body, i.Response.Body)
return fmt.Errorf("%w: status: %v found on %s. method: %s, url %s\nrequest body = %v\nresponse body = %v", ErrCassetteStatusMismatch, i.Response.Code, c.Name, i.Request.Method, i.Request.URL, i.Request.Body, i.Response.Body)
}
}

Expand Down Expand Up @@ -190,7 +195,7 @@ func TestAccCassettes_CheckOrphans(t *testing.T) {
// Look for cassettes with no matching test
for actualCassettePath := range actualCassettesPaths {
if _, ok := expectedCassettesPaths[actualCassettePath]; !ok {
cassetteWithNoTestErrs = append(cassetteWithNoTestErrs, fmt.Errorf("+ cassette [%s] has no matching test", actualCassettePath))
cassetteWithNoTestErrs = append(cassetteWithNoTestErrs, fmt.Errorf("%w + cassette [%s] has no matching test", ErrNoMatchingTest, actualCassettePath))
}
}

Expand Down
11 changes: 8 additions & 3 deletions internal/cdf/locality.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import (
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
)

var (
ErrMissingLocality = errors.New("missing locality zone or region to check IDs")
ErrDifferentLocality = errors.New("has different locality than the resource")
)

// expandListKeys return the list of keys for an attribute in a list
// example for private-networks.#.id in a list of size 2
// will return private-networks.0.id and private-networks.1.id
Expand Down Expand Up @@ -71,7 +76,7 @@ func LocalityCheck(keys ...string) schema.CustomizeDiffFunc {
l := getLocality(diff, m)

if l == "" {
return errors.New("missing locality zone or region to check IDs")
return ErrMissingLocality
}

for _, key := range keys {
Expand All @@ -82,13 +87,13 @@ func LocalityCheck(keys ...string) schema.CustomizeDiffFunc {
for _, listKey := range listKeys {
IDLocality, _, err := locality.ParseLocalizedID(diff.Get(listKey).(string))
if err == nil && !locality.CompareLocalities(IDLocality, l) {
return fmt.Errorf("given %s %s has different locality than the resource %q", listKey, diff.Get(listKey), l)
return fmt.Errorf("given %s %s %w %q", listKey, diff.Get(listKey), ErrDifferentLocality, l)
}
}
} else {
IDLocality, _, err := locality.ParseLocalizedID(diff.Get(key).(string))
if err == nil && !locality.CompareLocalities(IDLocality, l) {
return fmt.Errorf("given %s %s has different locality than the resource %q", key, diff.Get(key), l)
return fmt.Errorf("given %s %s %w %q", key, diff.Get(key), ErrDifferentLocality, l)
}
}
}
Expand Down
15 changes: 11 additions & 4 deletions internal/datasource/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)

var (
ErrMultipleElementsFound = errors.New("multiple elements found")
ErrNoElementFound = errors.New("no element found")
ErrMultipleMatches = errors.New("multiple matches")
ErrNoMatchesFound = errors.New("no matches found")
)

// FindExact finds the first element in 'slice' matching the condition defined by 'finder'.
// 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) {
Expand All @@ -20,7 +27,7 @@ func FindExact[T any](slice []T, finder func(T) bool, searchName string) (T, err
// More than one element found with the same search name
var zero T

return zero, fmt.Errorf("multiple elements found with the name %s", searchName)
return zero, fmt.Errorf("%w with the name %s", ErrMultipleElementsFound, searchName)
}

found = elem
Expand All @@ -31,7 +38,7 @@ func FindExact[T any](slice []T, finder func(T) bool, searchName string) (T, err
if !foundFlag {
var zero T

return zero, fmt.Errorf("no element found with the name %s", searchName)
return zero, fmt.Errorf("%w with the name %s", ErrNoElementFound, searchName)
}

return found, nil
Expand All @@ -41,10 +48,10 @@ func FindExact[T any](slice []T, finder func(T) bool, searchName string) (T, err
func SingularDataSourceFindError(resourceType string, err error) error {
if notFound(err) {
if errors.Is(err, &TooManyResultsError{}) {
return fmt.Errorf("multiple %[1]ss matched; use additional constraints to reduce matches to a single %[1]s", resourceType)
return fmt.Errorf("%w; use additional constraints to reduce matches to a single %[1]s", ErrMultipleMatches, resourceType)
}

return fmt.Errorf("no matching %[1]s found", resourceType)
return fmt.Errorf("%w", ErrNoMatchesFound)
}

return fmt.Errorf("reading %s: %w", resourceType, err)
Expand Down
6 changes: 5 additions & 1 deletion internal/httperrors/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import (
"github.com/stretchr/testify/assert"
)

var (
ErrNotAnHTTPError = errors.New("not an http error")
)

func TestIsHTTPCodeError(t *testing.T) {
assert.True(t, httperrors.IsHTTPCodeError(&scw.ResponseError{StatusCode: http.StatusBadRequest}, http.StatusBadRequest))
assert.False(t, httperrors.IsHTTPCodeError(nil, http.StatusBadRequest))
assert.False(t, httperrors.IsHTTPCodeError(&scw.ResponseError{StatusCode: http.StatusBadRequest}, http.StatusNotFound))
assert.False(t, httperrors.IsHTTPCodeError(errors.New("not an http error"), http.StatusNotFound))
assert.False(t, httperrors.IsHTTPCodeError(ErrNotAnHTTPError, http.StatusNotFound))
}

func TestIs404Error(t *testing.T) {
Expand Down
11 changes: 8 additions & 3 deletions internal/locality/parsing.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package locality

import (
"errors"
"fmt"
"strings"
)

var (
ErrInvalidLocalizedID = errors.New("invalid localized ID format")
)

// ParseLocalizedID parses a localizedID and extracts the resource locality and id.
func ParseLocalizedID(localizedID string) (locality, id string, err error) {
tab := strings.Split(localizedID, "/")
if len(tab) != 2 {
return "", localizedID, fmt.Errorf("cant parse localized id: %s", localizedID)
return "", localizedID, fmt.Errorf("%w: %s", ErrInvalidLocalizedID, localizedID)
}

return tab[0], tab[1], nil
Expand All @@ -19,7 +24,7 @@ func ParseLocalizedID(localizedID string) (locality, id string, err error) {
func ParseLocalizedNestedID(localizedID string) (locality string, innerID, outerID string, err error) {
tab := strings.Split(localizedID, "/")
if len(tab) < 3 {
return "", "", localizedID, fmt.Errorf("cant parse localized id: %s", localizedID)
return "", "", localizedID, fmt.Errorf("%w: %s", ErrInvalidLocalizedID, localizedID)
}

return tab[0], tab[1], strings.Join(tab[2:], "/"), nil
Expand All @@ -37,7 +42,7 @@ func ParseLocalizedNestedOwnerID(localizedID string) (locality string, innerID,
case 3:
locality, innerID, outerID, err = ParseLocalizedNestedID(localizedID)
default:
err = fmt.Errorf("cant parse localized id: %s", localizedID)
err = fmt.Errorf("%w: %s", ErrInvalidLocalizedID, localizedID)
}

if err != nil {
Expand Down
7 changes: 6 additions & 1 deletion internal/services/account/project_data_source_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package account_test

import (
"errors"
"fmt"
"strconv"
"testing"
Expand All @@ -9,6 +10,10 @@ import (
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
)

var (
ErrExpectedAtLeastOneProject = errors.New("expected at least one project")
)

const dummyOrgID = "AB7BD9BF-E1BD-41E8-9F1D-F16A2E3F3925"

func TestAccDataSourceProject_Basic(t *testing.T) {
Expand Down Expand Up @@ -151,7 +156,7 @@ func TestAccDataSourceProject_List(t *testing.T) {
}

if count < 1 {
return fmt.Errorf("expected at least one project, got %d", count)
return fmt.Errorf("%w, got %d", ErrExpectedAtLeastOneProject, count)
}

return nil
Expand Down
10 changes: 8 additions & 2 deletions internal/services/account/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package account_test

import (
"context"
"errors"
"fmt"
"testing"
"time"
Expand All @@ -15,6 +16,11 @@ import (
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
)

var (
ErrResourceNotFound = errors.New("resource not found")
ErrResourceStillExists = errors.New("resource still exists")
)

var DestroyWaitTimeout = 3 * time.Minute

func TestAccProject_Basic(t *testing.T) {
Expand Down Expand Up @@ -91,7 +97,7 @@ func isProjectPresent(tt *acctest.TestTools, name string) resource.TestCheckFunc
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("resource not found: %s", name)
return fmt.Errorf("%w: %s", ErrResourceNotFound, name)
}

accountAPI := account.NewProjectAPI(tt.Meta)
Expand Down Expand Up @@ -124,7 +130,7 @@ func isProjectDestroyed(tt *acctest.TestTools) resource.TestCheckFunc {

switch {
case err == nil:
return retry.RetryableError(fmt.Errorf("resource %s(%s) still exists", rs.Type, rs.Primary.ID))
return retry.RetryableError(fmt.Errorf("%w %s(%s)", ErrResourceStillExists, rs.Type, rs.Primary.ID))
case httperrors.Is404(err):
continue
default:
Expand Down
10 changes: 8 additions & 2 deletions internal/services/applesilicon/os_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import (
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
)

var (
ErrOSNotFound = errors.New("no OS found")
ErrOSNotFoundWithFilter = errors.New("no OS found with given filter")
)

func DataSourceOS() *schema.Resource {
return &schema.Resource{
ReadContext: DataSourceOSRead,
Expand Down Expand Up @@ -76,7 +81,7 @@ func DataSourceOSRead(ctx context.Context, d *schema.ResourceData, m any) diag.D
}

if res.TotalCount == 0 {
return diag.FromErr(errors.New("no OS found: something went wrong when listing OS"))
return diag.FromErr(fmt.Errorf("%w: something went wrong when listing OS", ErrOSNotFound))
}

for _, os := range res.Os {
Expand All @@ -89,7 +94,8 @@ func DataSourceOSRead(ctx context.Context, d *schema.ResourceData, m any) diag.D

if osID == "" {
return diag.FromErr(fmt.Errorf(
"no OS found with name=%q and version=%q in zone %s",
"%w with name=%q and version=%q in zone %s",
ErrOSNotFoundWithFilter,
d.Get("name"),
d.Get("version"),
zone,
Expand Down
Loading
Loading