driftctl/docs/new-remote-provider.md

5.9 KiB

Add a new remote provider

A remote provider in driftctl represents a cloud provider like AWS, GitHub, GCP or Azure.

Our current architecture allows to add a new provider in a few steps.

Declaring the new remote provider

First you need to create a new directory in pkg/remote/<provider name>. It will sit next to already implemented one like pkg/remote/aws.

Inside this directory, you will create a init.go file in which you will define the remote name constant:

const RemoteAWSTerraform = "aws+tf"

+tf means that we use Terraform to retrieve resource's details, in the future, we may add other ways to read those details.

You will then create a function to initialize the provider and all resource's enumerators. The best way to do it would be to copy the function signature from another provider:

func Init(
	// Version required by the user
	version string,
	// Util to send alert
	alerter *alerter.Alerter,
	// Library that contains all providers
	providerLibrary *terraform.ProviderLibrary,
	// Library that contains enumerators and details fetchers for each supported resources
	remoteLibrary *common.RemoteLibrary,
	// Progress displayer
	progress output.Progress,
	// Repository for all resource schemas
	resourceSchemaRepository *resource.SchemaRepository,
	// Factory used to create driftctl resource
	factory resource.ResourceFactory,
	// driftctl configuration directory (where Terraform provider is downloaded)
	configDir string) error {

	// You need to define the default version of the Terraform provider when the user does not specify one
	if version == "" {
		version = "3.19.0"
	}

	// Creation of the Terraform provider
	provider, err := NewAWSTerraformProvider(version, progress, configDir)
	if err != nil {
		return err
	}
	// And then initialization
	err = provider.Init()
	if err != nil {
		return err
	}

	// You'll need to create a new cache that will be used to cache fetched lists of resources
	repositoryCache := cache.New(100)

	// Deserializer is used to convert cty value returned by Terraform provider to driftctl Resource
    deserializer := resource.NewDeserializer(factory)

    // Adding the provider to the library
    providerLibrary.AddProvider(terraform.AWS, provider)
}

Once done, you'll create a provider.go file to contain your Terraform provider representation. Again you should look at other implementation:

// Define your actual provider representation, it is required to compose with terraform.TerraformProvider, a name and a version
// Please note that the name should match the real Terraform provider name.
type AWSTerraformProvider struct {
	*terraform.TerraformProvider
	session *session.Session
	name    string
	version string
}

func NewAWSTerraformProvider(version string, progress output.Progress, configDir string) (*AWSTerraformProvider, error) {
	// Just pass your version and name
	p := &AWSTerraformProvider{
		version: version,
		name:    "aws",
	}
	// Use Terraform ProviderInstaller to retrieve the provider if needed
	installer, err := tf.NewProviderInstaller(tf.ProviderConfig{
		Key:       p.name,
		Version:   version,
		ConfigDir: configDir,
	})
	if err != nil {
		return nil, err
	}
	p.session = session.Must(session.NewSessionWithOptions(session.Options{
		SharedConfigState: session.SharedConfigEnable,
	}))

	// ProviderConfig is dependent on the Terraform provider needs.
	tfProvider, err := terraform.NewTerraformProvider(installer, terraform.TerraformProviderConfig{
		Name:         p.name,
		DefaultAlias: *p.session.Config.Region,
		GetProviderConfig: func(alias string) interface{} {
			return awsConfig{
				Region:     alias,
				MaxRetries: 10,
			}
		},
	}, progress)
	if err != nil {
		return nil, err
	}
	p.TerraformProvider = tfProvider
	return p, err
}

func (a *AWSTerraformProvider) Name() string {
	return a.name
}

func (p *AWSTerraformProvider) Version() string {
	return p.version
}

The configuration returned in GetProviderConfig should be annotated with cty tags to be passed to the provider.

type githubConfig struct {
	Token        string
	Owner        string `cty:"owner"`
	Organization string
}

You are now almost done. You'll need to make driftctl aware of this provider. Thus, the in pkg/remote/remote.go file, add your new constant in supportedRemotes:

var supportedRemotes = []string{
	aws.RemoteAWSTerraform,
	github.RemoteGithubTerraform,
}

Don't forget to modify the Activate function. You'll need to add a new case in the switch statement:

func Activate(remote, version string, alerter *alerter.Alerter,
	providerLibrary *terraform.ProviderLibrary,
	remoteLibrary *common.RemoteLibrary,
	progress output.Progress,
	resourceSchemaRepository *resource.SchemaRepository,
	factory resource.ResourceFactory,
	configDir string) error {
	switch remote {
	case aws.RemoteAWSTerraform:
		return aws.Init(version, alerter, providerLibrary, remoteLibrary, progress, resourceSchemaRepository, factory, configDir)
	case github.RemoteGithubTerraform:
		return github.Init(version, alerter, providerLibrary, remoteLibrary, progress, resourceSchemaRepository, factory, configDir)
	default:
		return errors.Errorf("unsupported remote '%s'", remote)
	}
}

Your provider is now set up!

Prepare driftctl to support new resources

Each new resource of the newly added provider will be located in pkg/resource/<provider name> directory. You need to create the latter and the metadatas.go file inside it.

Inside this file add a new function:

func InitResourcesMetadata(resourceSchemaRepository resource.SchemaRepositoryInterface) {
}

Then, add a call to this function in the remote/<provider>/init.go file you created in the first step.

You also need to create a test schema for upcoming tests.

Please use TestCreateNewSchema located in test/terraform/schemas_test.go to generate a schema file that will be used for the mocked provider.

Everything is now ready, you should start adding new resources!