Skip to content

Commit

Permalink
chore: migrate meta to its own package (#2449)
Browse files Browse the repository at this point in the history
* chore: migrate meta to itw own package

* chore: migrate version to its own package

* chore: migrate meta to its own package

* fix unecessary casts

* Fix

* mute R014 linter for tfproviderlint
  • Loading branch information
remyleone committed Mar 13, 2024
1 parent 656b834 commit 472efcb
Show file tree
Hide file tree
Showing 247 changed files with 1,838 additions and 1,845 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tfproviderlint.yml
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Install tfproviderlint
run: go install github.com/bflad/tfproviderlint/cmd/tfproviderlint@latest
- name: Run tfproviderlint
run: tfproviderlint ./...
run: tfproviderlint -R014=false ./...
tfproviderdocs:
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 3 additions & 3 deletions cmd/tftemplate/datasource.go.tmpl
Expand Up @@ -29,8 +29,8 @@ func dataSourceScaleway{{.Resource}}() *schema.Resource {
}
}

func dataSourceScaleway{{.Resource}}Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api, {{.Locality}}, err := {{.API}}APIWith{{.LocalityUpper}}(d, meta)
func dataSourceScaleway{{.Resource}}Read(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api, {{.Locality}}, err := {{.API}}APIWith{{.LocalityUpper}}(d, m)
if err != nil {
return diag.FromErr(err)
}
Expand Down Expand Up @@ -65,7 +65,7 @@ func dataSourceScaleway{{.Resource}}Read(ctx context.Context, d *schema.Resource
return diag.FromErr(err)
}

diags := resourceScaleway{{.Resource}}Read(ctx, d, meta)
diags := resourceScaleway{{.Resource}}Read(ctx, d, m)
if diags != nil {
return append(diags, diag.Errorf("failed to read {{.ResourceCleanLow}} state")...)
}
Expand Down
8 changes: 3 additions & 5 deletions cmd/tftemplate/helpers.go.tmpl
Expand Up @@ -8,10 +8,9 @@ import (

// {{.API}}APIWith{{.LocalityUpper}} returns a new {{.API}} API and the {{.Locality}} for a Create request
func {{.API}}APIWith{{.LocalityUpper}}(d *schema.ResourceData, m interface{}) (*{{.API}}.API, scw.{{.LocalityUpper}}, error) {
meta := m.(*Meta)
{{.API}}API := {{.API}}.NewAPI(meta.scwClient)
{{.API}}API := {{.API}}.NewAPI(meta.ExtractScwClient(m))

{{.Locality}}, err := extract{{.LocalityUpper}}(d, meta)
{{.Locality}}, err := extract{{.LocalityUpper}}(d, m)
if err != nil {
return nil, "", err
}
Expand All @@ -21,8 +20,7 @@ func {{.API}}APIWith{{.LocalityUpper}}(d *schema.ResourceData, m interface{}) (*

// {{.API}}APIWith{{.LocalityAdjectiveUpper}}AndID returns a new {{.API }} API with {{.Locality}} and ID extracted from the state
func {{.API}}APIWith{{.LocalityUpper}}AndID(m interface{}, {{.LocalityAdjective}}ID string) (*{{.API}}.API, scw.{{.LocalityUpper}}, string, error) {
meta := m.(*Meta)
{{.API}}API := {{.API}}.NewAPI(meta.scwClient)
{{.API}}API := {{.API}}.NewAPI(meta.ExtractScwClient(m))

{{.Locality}}, ID, err := parse{{.LocalityAdjectiveUpper}}ID({{.LocalityAdjective}}ID)
if err != nil {
Expand Down
20 changes: 10 additions & 10 deletions cmd/tftemplate/resource.go.tmpl
Expand Up @@ -33,8 +33,8 @@ func resourceScaleway{{ .Resource }}() *schema.Resource {
}
}

func resourceScaleway{{ .Resource }}Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api, {{ .Locality }}, err := {{ .API }}APIWith{{ .LocalityUpper }}(d, meta)
func resourceScaleway{{ .Resource }}Create(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api, {{ .Locality }}, err := {{ .API }}APIWith{{ .LocalityUpper }}(d, m)
if err != nil {
return diag.FromErr(err)
}
Expand All @@ -57,11 +57,11 @@ func resourceScaleway{{ .Resource }}Create(ctx context.Context, d *schema.Resour
}
{{end}}

return resourceScaleway{{ .Resource }}Read(ctx, d, meta)
return resourceScaleway{{ .Resource }}Read(ctx, d, m)
}

func resourceScaleway{{ .Resource }}Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api, {{ .Locality }}, id, err := {{ .API }}APIWith{{ .LocalityUpper }}AndID(meta, d.Id())
func resourceScaleway{{ .Resource }}Read(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api, {{ .Locality }}, id, err := {{ .API }}APIWith{{ .LocalityUpper }}AndID(m, d.Id())
if err != nil {
return diag.FromErr(err)
}
Expand All @@ -88,8 +88,8 @@ func resourceScaleway{{ .Resource }}Read(ctx context.Context, d *schema.Resource
return nil
}

func resourceScaleway{{ .Resource }}Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api, {{ .Locality }}, id, err := {{ .API }}APIWith{{ .LocalityUpper }}AndID(meta, d.Id())
func resourceScaleway{{ .Resource }}Update(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api, {{ .Locality }}, id, err := {{ .API }}APIWith{{ .LocalityUpper }}AndID(m, d.Id())
if err != nil {
return diag.FromErr(err)
}
Expand All @@ -116,11 +116,11 @@ func resourceScaleway{{ .Resource }}Update(ctx context.Context, d *schema.Resour
return diag.FromErr(err)
}

return resourceScaleway{{ .Resource }}Read(ctx, d, meta)
return resourceScaleway{{ .Resource }}Read(ctx, d, m)
}

func resourceScaleway{{ .Resource }}Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api, {{ .Locality }}, id, err := {{ .API }}APIWith{{ .LocalityUpper }}AndID(meta, d.Id())
func resourceScaleway{{ .Resource }}Delete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
api, {{ .Locality }}, id, err := {{ .API }}APIWith{{ .LocalityUpper }}AndID(m, d.Id())
if err != nil {
return diag.FromErr(err)
}
Expand Down
6 changes: 6 additions & 0 deletions internal/meta/errors.go
@@ -0,0 +1,6 @@
package meta

import "errors"

// ErrProjectIDNotFound is returned when no region can be detected
var ErrProjectIDNotFound = errors.New("could not detect project id")
98 changes: 98 additions & 0 deletions internal/meta/extractors.go
@@ -0,0 +1,98 @@
package meta

import (
"net/http"

"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/zonal"
)

// terraformResourceData is an interface for *schema.ResourceData. (used for mock)
type terraformResourceData interface {
HasChange(string) bool
GetOk(string) (interface{}, bool)
Get(string) interface{}
Id() string
}

// ExtractZone will try to guess the zone from the following:
// - zone field of the resource data
// - default zone from config
func ExtractZone(d terraformResourceData, m interface{}) (scw.Zone, error) {
rawZone, exist := d.GetOk("zone")
if exist {
return scw.ParseZone(rawZone.(string))
}

zone, exist := m.(*Meta).ScwClient().GetDefaultZone()
if exist {
return zone, nil
}

return "", zonal.ErrZoneNotFound
}

// ExtractRegion will try to guess the region from the following:
// - region field of the resource data
// - default region from config
func ExtractRegion(d terraformResourceData, m interface{}) (scw.Region, error) {
rawRegion, exist := d.GetOk("region")
if exist {
return scw.ParseRegion(rawRegion.(string))
}

region, exist := m.(*Meta).ScwClient().GetDefaultRegion()
if exist {
return region, nil
}

return "", regional.ErrRegionNotFound
}

// ExtractRegionWithDefault will try to guess the region from the following:
// - region field of the resource data
// - default region given in argument
// - default region from config
func ExtractRegionWithDefault(d terraformResourceData, m interface{}, defaultRegion scw.Region) (scw.Region, error) {
rawRegion, exist := d.GetOk("region")
if exist {
return scw.ParseRegion(rawRegion.(string))
}

if defaultRegion != "" {
return defaultRegion, nil
}

region, exist := m.(*Meta).ScwClient().GetDefaultRegion()
if exist {
return region, nil
}

return "", regional.ErrRegionNotFound
}

// ExtractProjectID will try to guess the project id from the following:
// - project_id field of the resource data
// - default project id from config
func ExtractProjectID(d terraformResourceData, m interface{}) (projectID string, isDefault bool, err error) {
rawProjectID, exist := d.GetOk("project_id")
if exist {
return rawProjectID.(string), false, nil
}

defaultProjectID, exist := m.(*Meta).ScwClient().GetDefaultProjectID()
if exist {
return defaultProjectID, true, nil
}

return "", false, ErrProjectIDNotFound
}

func ExtractScwClient(m interface{}) *scw.Client {
return m.(*Meta).ScwClient()
}

func ExtractHTTPClient(m interface{}) *http.Client {
return m.(*Meta).HTTPClient()
}
185 changes: 185 additions & 0 deletions internal/meta/meta.go
@@ -0,0 +1,185 @@
package meta

import (
"context"
"fmt"
"net/http"
"os"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/transport"
"github.com/scaleway/terraform-provider-scaleway/v2/version"
)

const appendUserAgentEnvVar = "TF_APPEND_USER_AGENT"

// Meta contains config and SDK clients used by resources.
//
// This meta value is passed into all resources.
type Meta struct {
// scwClient is the Scaleway SDK client.
scwClient *scw.Client
// httpClient can be either a regular http.Client used to make real HTTP requests
// or it can be a http.Client used to record and replay cassettes which is useful
// to replay recorded interactions with APIs locally
httpClient *http.Client
}

func (m Meta) ScwClient() *scw.Client {
return m.scwClient
}

func (m Meta) HTTPClient() *http.Client {
return m.httpClient
}

type Config struct {
ProviderSchema *schema.ResourceData
TerraformVersion string
ForceZone scw.Zone
ForceProjectID string
ForceOrganizationID string
ForceAccessKey string
ForceSecretKey string
HTTPClient *http.Client
}

// providerConfigure creates the Meta object containing the SDK client.
func NewMeta(ctx context.Context, config *Config) (*Meta, error) {
////
// Load Profile
////
profile, err := loadProfile(ctx, config.ProviderSchema)
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)
}

// TODO validated profile

////
// Create scaleway SDK client
////
opts := []scw.ClientOption{
scw.WithUserAgent(customizeUserAgent(version.Version, config.TerraformVersion)),
scw.WithProfile(profile),
}

httpClient := &http.Client{Transport: transport.NewRetryableTransport(http.DefaultTransport)}
if config.HTTPClient != nil {
httpClient = config.HTTPClient
}
opts = append(opts, scw.WithHTTPClient(httpClient))

scwClient, err := scw.NewClient(opts...)
if err != nil {
return nil, err
}

return &Meta{
scwClient: scwClient,
httpClient: httpClient,
}, nil
}

func customizeUserAgent(providerVersion string, terraformVersion string) string {
userAgent := fmt.Sprintf("terraform-provider/%s terraform/%s", providerVersion, terraformVersion)

if appendUserAgent := os.Getenv(appendUserAgentEnvVar); appendUserAgent != "" {
userAgent += " " + appendUserAgent
}

return userAgent
}

//gocyclo:ignore
func loadProfile(ctx context.Context, d *schema.ResourceData) (*scw.Profile, error) {
config, err := scw.LoadConfig()
// If the config file do not exist, don't return an error as we may find config in ENV or flags.
if _, isNotFoundError := err.(*scw.ConfigFileNotFoundError); isNotFoundError {
config = &scw.Config{}
} else if err != nil {
return nil, err
}

// By default we set default zone and region to fr-par
defaultZoneProfile := &scw.Profile{
DefaultRegion: scw.StringPtr(scw.RegionFrPar.String()),
DefaultZone: scw.StringPtr(scw.ZoneFrPar1.String()),
}

activeProfile, err := config.GetActiveProfile()
if err != nil {
return nil, err
}
envProfile := scw.LoadEnvProfile()

providerProfile := &scw.Profile{}
if d != nil {
if profileName, exist := d.GetOk("profile"); exist {
profileFromConfig, err := config.GetProfile(profileName.(string))
if err == nil {
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))
}
}

profile := scw.MergeProfiles(defaultZoneProfile, activeProfile, providerProfile, envProfile)

// If profile have a defaultZone but no defaultRegion we set the defaultRegion
// to the one of the defaultZone
if profile.DefaultZone != nil && *profile.DefaultZone != "" &&
(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())
} else {
tflog.Debug(ctx, "cannot guess region: "+err.Error())
}
}
return profile, nil
}

0 comments on commit 472efcb

Please sign in to comment.