diff --git a/cmd/provider/main.go b/cmd/provider/main.go index 381a735..353b095 100644 --- a/cmd/provider/main.go +++ b/cmd/provider/main.go @@ -32,7 +32,6 @@ import ( "github.com/crossplane/crossplane-runtime/pkg/resource" "github.com/crossplane/crossplane-runtime/pkg/statemetrics" upcontroller "github.com/crossplane/upjet/pkg/controller" - "github.com/crossplane/upjet/pkg/terraform" "gopkg.in/alecthomas/kingpin.v2" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,14 +52,16 @@ import ( func main() { var ( - app = kingpin.New(filepath.Base(os.Args[0]), "Terraform based Crossplane provider for Equinix").DefaultEnvars() - debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool() - syncInterval = app.Flag("sync", "Sync interval controls how often all resources will be double checked for drift.").Short('s').Default("1h").Duration() - pollInterval = app.Flag("poll", "Poll interval controls how often an individual resource should be checked for drift.").Default("10m").Duration() - leaderElection = app.Flag("leader-election", "Use leader election for the controller manager.").Short('l').Default("false").OverrideDefaultFromEnvar("LEADER_ELECTION").Bool() - pollStateMetricInterval = app.Flag("poll-state-metric", "State metric recording interval").Default("5s").Duration() - maxReconcileRate = app.Flag("max-reconcile-rate", "The global maximum rate per second at which resources may checked for drift from the desired state.").Default("10").Int() - + app = kingpin.New(filepath.Base(os.Args[0]), "Terraform based Crossplane provider for Equinix").DefaultEnvars() + debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool() + syncInterval = app.Flag("sync", "Sync interval controls how often all resources will be double checked for drift.").Short('s').Default("1h").Duration() + pollInterval = app.Flag("poll", "Poll interval controls how often an individual resource should be checked for drift.").Default("10m").Duration() + leaderElection = app.Flag("leader-election", "Use leader election for the controller manager.").Short('l').Default("false").OverrideDefaultFromEnvar("LEADER_ELECTION").Bool() + terraformVersion = app.Flag("terraform-version", "Terraform version.").Required().Envar("TERRAFORM_VERSION").String() + providerSource = app.Flag("terraform-provider-source", "Terraform provider source.").Required().Envar("TERRAFORM_PROVIDER_SOURCE").String() + providerVersion = app.Flag("terraform-provider-version", "Terraform provider version.").Required().Envar("TERRAFORM_PROVIDER_VERSION").String() + pollStateMetricInterval = app.Flag("poll-state-metric", "State metric recording interval").Default("5s").Duration() + maxReconcileRate = app.Flag("max-reconcile-rate", "The global maximum rate per second at which resources may checked for drift from the desired state.").Default("10").Int() namespace = app.Flag("namespace", "Namespace used to set as default scope in default secret store config.").Default("crossplane-system").Envar("POD_NAMESPACE").String() enableExternalSecretStores = app.Flag("enable-external-secret-stores", "Enable support for ExternalSecretStores.").Default("false").Envar("ENABLE_EXTERNAL_SECRET_STORES").Bool() essTLSCertsPath = app.Flag("ess-tls-cert-dir", "Path of ESS TLS certificates.").Envar("ESS_TLS_CERTS_DIR").String() @@ -112,6 +113,12 @@ func main() { } ctx := context.Background() provider, err := config.GetProvider(ctx, false) + setupCfg := clients.SetupConfig{ + ProviderVersion: providerVersion, + TerraformVersion: terraformVersion, + ProviderSource: providerSource, + TerraformProvider: provider.TerraformProvider, + } kingpin.FatalIfError(err, "Cannot initialize the provider configuration") o := upcontroller.Options{ Options: xpcontroller.Options{ @@ -122,11 +129,8 @@ func main() { Features: &feature.Flags{}, MetricOptions: &mo, }, - Provider: provider, - // use the following WorkspaceStoreOption to enable the shared gRPC mode - // terraform.WithProviderRunner(terraform.NewSharedProvider(log, os.Getenv("TERRAFORM_NATIVE_PROVIDER_PATH"), terraform.WithNativeProviderArgs("-debuggable"))) - WorkspaceStore: terraform.NewWorkspaceStore(log), - SetupFn: clients.TerraformSetupBuilder(provider.TerraformProvider), + Provider: provider, + SetupFn: clients.TerraformSetupBuilder(setupCfg), PollJitter: pollJitter, OperationTrackerStore: upcontroller.NewOperationStore(log), } diff --git a/internal/clients/equinix.go b/internal/clients/equinix.go index bda40e0..e5144ff 100644 --- a/internal/clients/equinix.go +++ b/internal/clients/equinix.go @@ -21,13 +21,16 @@ import ( "encoding/json" "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/upjet/pkg/terraform" + equinixprovider "github.com/equinix/terraform-provider-equinix/equinix/provider" + + "github.com/equinix/terraform-provider-equinix/version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/crossplane/upjet/pkg/terraform" - "github.com/crossplane-contrib/provider-jet-equinix/apis/v1alpha1" ) @@ -50,12 +53,10 @@ const ( ) type SetupConfig struct { - NativeProviderPath *string - NativeProviderSource *string - NativeProviderVersion *string - TerraformVersion *string - DefaultScheduler terraform.ProviderScheduler - TerraformProvider *schema.Provider + ProviderSource *string + ProviderVersion *string + TerraformVersion *string + TerraformProvider *schema.Provider } func prepareTerraformProviderConfiguration(creds map[string]string, pc v1alpha1.ProviderConfiguration) map[string]any { @@ -85,9 +86,15 @@ func prepareTerraformProviderConfiguration(creds map[string]string, pc v1alpha1. // TerraformSetupBuilder builds Terraform a terraform.SetupFn function which // returns Terraform provider setup configuration -func TerraformSetupBuilder(tfProvider *schema.Provider) terraform.SetupFn { +func TerraformSetupBuilder(setupCfg SetupConfig) terraform.SetupFn { return func(ctx context.Context, client client.Client, mg resource.Managed) (terraform.Setup, error) { - ps := terraform.Setup{} + ps := terraform.Setup{ + Version: *setupCfg.TerraformVersion, + Requirement: terraform.ProviderRequirement{ + Source: *setupCfg.ProviderSource, + Version: *setupCfg.ProviderVersion, + }, + } configRef := mg.GetProviderConfigReference() if configRef == nil { @@ -113,6 +120,25 @@ func TerraformSetupBuilder(tfProvider *schema.Provider) terraform.SetupFn { } ps.Configuration = prepareTerraformProviderConfiguration(equinixCreds, pc.Spec.Configuration) - return ps, nil + return ps, errors.Wrap(configureNoForkEquinixClient(ctx, &ps, *setupCfg.TerraformProvider), "failed to configure the no-fork equinix client") } } + +func configureNoForkEquinixClient(ctx context.Context, ps *terraform.Setup, p schema.Provider) error { + // Please be aware that this implementation relies on the schema.Provider + // parameter `p` being a non-pointer. This is because normally + // the Terraform plugin SDK normally configures the provider + // only once and using a pointer argument here will cause + // race conditions between resources referring to different + // ProviderConfigs. + diag := p.Configure(context.WithoutCancel(ctx), &terraformsdk.ResourceConfig{ + Config: ps.Configuration, + }) + if diag != nil && diag.HasError() { + return errors.Errorf("failed to configure the provider: %v", diag) + } + + fwProvider := equinixprovider.CreateFrameworkProvider(version.ProviderVersion) + ps.FrameworkProvider = fwProvider + return nil +}