Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add scaleway provider with scaleway_function_namespace resource support #1648

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<p align="center">
Measures infrastructure as code coverage, and tracks infrastructure drift.<br>
<strong>IaC:</strong> Terraform. <strong>Cloud providers:</strong> AWS, GitHub, Azure, GCP.<br>
<strong>IaC:</strong> Terraform. <strong>Cloud providers:</strong> AWS, GitHub, Azure, GCP, Scaleway (alpha).<br>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added Scaleway in the list of providers here (but advertised it's still in alpha since only one resource is available). Tell me if we should wait before putting it in the README!

:warning: <strong>This tool is still in beta state and will evolve in the future with potential breaking changes</strong> :warning:
</p>

Expand Down
18 changes: 10 additions & 8 deletions enumeration/remote/common/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import (
type RemoteParameter string

const (
RemoteAWSTerraform = "aws+tf"
RemoteGithubTerraform = "github+tf"
RemoteGoogleTerraform = "gcp+tf"
RemoteAzureTerraform = "azure+tf"
RemoteAWSTerraform = "aws+tf"
RemoteGithubTerraform = "github+tf"
RemoteGoogleTerraform = "gcp+tf"
RemoteAzureTerraform = "azure+tf"
RemoteScalewayTerraform = "scaleway+tf"
)

var remoteParameterMapping = map[RemoteParameter]string{
RemoteAWSTerraform: tf.AWS,
RemoteGithubTerraform: tf.GITHUB,
RemoteGoogleTerraform: tf.GOOGLE,
RemoteAzureTerraform: tf.AZURE,
RemoteAWSTerraform: tf.AWS,
RemoteGithubTerraform: tf.GITHUB,
RemoteGoogleTerraform: tf.GOOGLE,
RemoteAzureTerraform: tf.AZURE,
RemoteScalewayTerraform: tf.SCALEWAY,
}

func (p RemoteParameter) GetProviderAddress() *lock.ProviderAddress {
Expand Down
4 changes: 4 additions & 0 deletions enumeration/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/snyk/driftctl/enumeration/remote/common"
"github.com/snyk/driftctl/enumeration/remote/github"
"github.com/snyk/driftctl/enumeration/remote/google"
"github.com/snyk/driftctl/enumeration/remote/scaleway"
"github.com/snyk/driftctl/enumeration/resource"
"github.com/snyk/driftctl/enumeration/terraform"
)
Expand All @@ -18,6 +19,7 @@ var supportedRemotes = []string{
common.RemoteGithubTerraform,
common.RemoteGoogleTerraform,
common.RemoteAzureTerraform,
common.RemoteScalewayTerraform,
}

func IsSupported(remote string) bool {
Expand All @@ -39,6 +41,8 @@ func Activate(remote, version string, alerter alerter.AlerterInterface, provider
return google.Init(version, alerter, providerLibrary, remoteLibrary, progress, factory, configDir)
case common.RemoteAzureTerraform:
return azurerm.Init(version, alerter, providerLibrary, remoteLibrary, progress, factory, configDir)
case common.RemoteScalewayTerraform:
return scaleway.Init(version, alerter, providerLibrary, remoteLibrary, progress, factory, configDir)

default:
return errors.Errorf("unsupported remote '%s'", remote)
Expand Down
65 changes: 65 additions & 0 deletions enumeration/remote/scaleway/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package client

import (
"errors"
"fmt"

"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/snyk/driftctl/pkg/version"
)

// Create - Creates a new Scaleway client object
// Heavily inspired by the createClient method in Scaleway CLI:
// https://github.com/scaleway/scaleway-cli/blob/v2.13.0/internal/core/client.go#L15-L83
func Create() (*scw.Client, error) {

profile := scw.LoadEnvProfile()

// Default path is based on the following priority order:
// * $SCW_CONFIG_PATH
// * $XDG_CONFIG_HOME/scw/config.yaml
// * $HOME/.config/scw/config.yaml
// * $USERPROFILE/.config/scw/config.yaml
var errConfigFileNotFound *scw.ConfigFileNotFoundError
config, err := scw.LoadConfigFromPath(scw.GetConfigPath())

switch {
case errors.As(err, &errConfigFileNotFound):
break
case err != nil:
return nil, err
default:
// If a config file is found and loaded, we merge with env
activeProfile, err := config.GetActiveProfile()
if err != nil {
return nil, err
}

// Creates a client from the active profile
// It will trigger a validation step on its configuration to catch errors if any
opts := []scw.ClientOption{
scw.WithProfile(activeProfile),
}

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

profile = scw.MergeProfiles(activeProfile, profile)
}

opts := []scw.ClientOption{
scw.WithDefaultRegion(scw.RegionFrPar),
scw.WithDefaultZone(scw.ZoneFrPar1),
scw.WithUserAgent(fmt.Sprintf("driftctl/%s", version.Current())),
scw.WithProfile(profile),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the profile contains a region (e.g. fr-par, nl-ams, pl-waw), the client will use this region instead of the default fr-par. Also, since one client can only process resources on one region, when listing resources (e.g. function namespaces), we'll only get resources from that specific region. I took a look at other providers like AWS, and it looks like it's the same behavior. Could you confirm? πŸ™

}

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

return client, nil
}
46 changes: 46 additions & 0 deletions enumeration/remote/scaleway/function_namespace_enumerator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package scaleway

import (
remoteerror "github.com/snyk/driftctl/enumeration/remote/error"
"github.com/snyk/driftctl/enumeration/remote/scaleway/repository"
"github.com/snyk/driftctl/enumeration/resource"
"github.com/snyk/driftctl/pkg/resource/scaleway"
)

type FunctionNamespaceEnumerator struct {
repository repository.FunctionRepository
factory resource.ResourceFactory
}

func NewFunctionNamespaceEnumerator(repo repository.FunctionRepository, factory resource.ResourceFactory) *FunctionNamespaceEnumerator {
return &FunctionNamespaceEnumerator{
repository: repo,
factory: factory,
}
}

func (e *FunctionNamespaceEnumerator) SupportedType() resource.ResourceType {
return scaleway.ScalewayFunctionNamespaceResourceType
}

func (e *FunctionNamespaceEnumerator) Enumerate() ([]*resource.Resource, error) {
namespaces, err := e.repository.ListAllNamespaces()
if err != nil {
return nil, remoteerror.NewResourceListingError(err, string(e.SupportedType()))
}

results := make([]*resource.Resource, 0, len(namespaces))

for _, namespace := range namespaces {
results = append(
results,
e.factory.CreateAbstractResource(
string(e.SupportedType()),
getRegionalID(namespace.Region.String(), namespace.ID),
map[string]interface{}{},
),
)
}

return results, err
}
47 changes: 47 additions & 0 deletions enumeration/remote/scaleway/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package scaleway

import (
"github.com/snyk/driftctl/enumeration"
"github.com/snyk/driftctl/enumeration/alerter"
"github.com/snyk/driftctl/enumeration/remote/cache"
"github.com/snyk/driftctl/enumeration/remote/common"
"github.com/snyk/driftctl/enumeration/remote/scaleway/client"
"github.com/snyk/driftctl/enumeration/remote/scaleway/repository"
"github.com/snyk/driftctl/enumeration/resource"
"github.com/snyk/driftctl/enumeration/terraform"
"github.com/snyk/driftctl/pkg/resource/scaleway"
)

func Init(version string, alerter alerter.AlerterInterface, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {

if version == "" {
version = "2.14.1"
}

provider, err := NewScalewayTerraformProvider(version, progress, configDir)
if err != nil {
return err
}
err = provider.Init()
if err != nil {
return err
}

providerLibrary.AddProvider(terraform.SCALEWAY, provider)

scwClient, err := client.Create()
if err != nil {
return err
}

repositoryCache := cache.New(100)

funcRepository := repository.NewFunctionRepository(scwClient, repositoryCache)

deserializer := resource.NewDeserializer(factory)

remoteLibrary.AddEnumerator(NewFunctionNamespaceEnumerator(funcRepository, factory))
remoteLibrary.AddDetailsFetcher(scaleway.ScalewayFunctionNamespaceResourceType, common.NewGenericDetailsFetcher(scaleway.ScalewayFunctionNamespaceResourceType, provider, deserializer))

return nil
}
47 changes: 47 additions & 0 deletions enumeration/remote/scaleway/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package scaleway

import (
"github.com/snyk/driftctl/enumeration"
"github.com/snyk/driftctl/enumeration/remote/terraform"
tf "github.com/snyk/driftctl/enumeration/terraform"
)

type ScalewayTerraformProvider struct {
*terraform.TerraformProvider
name string
version string
}

func NewScalewayTerraformProvider(version string, progress enumeration.ProgressCounter, configDir string) (*ScalewayTerraformProvider, error) {

provider := &ScalewayTerraformProvider{
version: version,
name: "scaleway",
}

installer, err := tf.NewProviderInstaller(tf.ProviderConfig{
Key: provider.name,
Version: version,
ConfigDir: configDir,
})
if err != nil {
return nil, err
}

tfProvider, err := terraform.NewTerraformProvider(installer, terraform.TerraformProviderConfig{
Name: provider.name,
}, progress)
if err != nil {
return nil, err
}
provider.TerraformProvider = tfProvider
return provider, err
}

func (p *ScalewayTerraformProvider) Name() string {
return p.name
}

func (p *ScalewayTerraformProvider) Version() string {
return p.version
}
48 changes: 48 additions & 0 deletions enumeration/remote/scaleway/repository/function_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package repository

import (
"github.com/scaleway/scaleway-sdk-go/api/function/v1beta1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/snyk/driftctl/enumeration/remote/cache"
)

type FunctionRepository interface {
ListAllNamespaces() ([]*function.Namespace, error)
}

// We create an interface here (mainly for mocking purpose) because in scaleway-sdk-go, API is a struct and not an interface
type functionAPI interface {
ListNamespaces(req *function.ListNamespacesRequest, opts ...scw.RequestOption) (*function.ListNamespacesResponse, error)
}

type functionRepository struct {
client functionAPI
cache cache.Cache
}

func NewFunctionRepository(client *scw.Client, c cache.Cache) *functionRepository {

api := function.NewAPI(client)
return &functionRepository{
api,
c,
}
}

func (r *functionRepository) ListAllNamespaces() ([]*function.Namespace, error) {
if v := r.cache.Get("functionListAllNamespaces"); v != nil {
return v.([]*function.Namespace), nil
}

req := &function.ListNamespacesRequest{}
res, err := r.client.ListNamespaces(req)
if err != nil {
return nil, err
}

namespaces := res.Namespaces

r.cache.Put("functionListAllNamespaces", namespaces)

return namespaces, err
}
Loading