Skip to content

Commit

Permalink
Switch no-fork architecture
Browse files Browse the repository at this point in the history
Signed-off-by: Fatih Türken <[email protected]>
Signed-off-by: Sergen Yalçın <[email protected]>
  • Loading branch information
turkenf authored and sergenyalcin committed Jan 21, 2025
1 parent fe643b0 commit b3d1382
Show file tree
Hide file tree
Showing 13 changed files with 286 additions and 111 deletions.
6 changes: 5 additions & 1 deletion cmd/generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ Copyright 2021 Upbound Inc.
package main

import (
"context"
"fmt"
"os"
"path/filepath"

"github.com/crossplane/upjet/pkg/pipeline"
"gopkg.in/alecthomas/kingpin.v2"

"github.com/upbound/provider-azapi/config"
)
Expand All @@ -23,5 +25,7 @@ func main() {
if err != nil {
panic(fmt.Sprintf("cannot calculate the absolute path with %s", rootDir))
}
pipeline.Run(config.GetProvider(), absRootDir)
p, err := config.GetProvider(context.Background(), true)
kingpin.FatalIfError(err, "Cannot initialize the provider configuration")
pipeline.Run(p, absRootDir)
}
50 changes: 33 additions & 17 deletions cmd/provider/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package main

import (
"context"
"fmt"
"io"
"log"
"os"
"path/filepath"
"time"
Expand All @@ -21,7 +24,6 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/crossplane-runtime/pkg/statemetrics"
tjcontroller "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"
Expand All @@ -41,17 +43,24 @@ import (

func main() {
var (
app = kingpin.New(filepath.Base(os.Args[0]), "Terraform based Crossplane provider for AzAPI").DefaultEnvars()
app = kingpin.New(filepath.Base(os.Args[0]), "Terraform based Crossplane provider for AzAPI").DefaultEnvars()
deprecationAction = func(flagName string) kingpin.Action {
return func(c *kingpin.ParseContext) error {
_, err := fmt.Fprintf(os.Stderr, "warning: Command-line flag %q is deprecated and no longer used. It will be removed in a future release. Please remove it from all of your configurations (ControllerConfigs, etc.).\n", flagName)
kingpin.FatalIfError(err, "Failed to print the deprecation notice.")
return nil
}
}
debug = app.Flag("debug", "Run with debug logging.").Short('d').Bool()
syncPeriod = app.Flag("sync", "Controller manager sync period such as 300ms, 1.5h, or 2h45m").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()
pollStateMetricInterval = app.Flag("poll-state-metric", "State metric recording interval").Default("5s").Duration()
leaderElection = app.Flag("leader-election", "Use leader election for the controller manager.").Short('l').Default("false").OverrideDefaultFromEnvar("LEADER_ELECTION").Bool()
maxReconcileRate = app.Flag("max-reconcile-rate", "The global maximum rate per second at which resources may be checked for drift from the desired state.").Default("10").Int()

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()
_ = app.Flag("terraform-version", "Terraform version.").Envar("TERRAFORM_VERSION").Hidden().Action(deprecationAction("terraform-version")).String()
_ = app.Flag("terraform-provider-source", "Terraform provider source.").Envar("TERRAFORM_PROVIDER_SOURCE").Hidden().Action(deprecationAction("terraform-provider-source")).String()
_ = app.Flag("terraform-provider-version", "Terraform provider version.").Envar("TERRAFORM_PROVIDER_VERSION").Hidden().Action(deprecationAction("terraform-provider-version")).String()

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()
Expand All @@ -60,17 +69,22 @@ func main() {
)

kingpin.MustParse(app.Parse(os.Args[1:]))
log.Default().SetOutput(io.Discard)
ctrl.SetLogger(zap.New(zap.WriteTo(io.Discard)))

zl := zap.New(zap.UseDevMode(*debug))
log := logging.NewLogrLogger(zl.WithName("provider-azapi"))
logr := logging.NewLogrLogger(zl.WithName("provider-azapi"))
if *debug {
// The controller-runtime runs with a no-op logger by default. It is
// *very* verbose even at info level, so we only provide it a real
// logger when we're running in debug mode.
ctrl.SetLogger(zl)
}

log.Debug("Starting", "sync-period", syncPeriod.String(), "poll-interval", pollInterval.String(), "max-reconcile-rate", *maxReconcileRate)
// currently, we configure the jitter to be the 5% of the poll interval
pollJitter := time.Duration(float64(*pollInterval) * 0.05)
logr.Debug("Starting", "sync-period", syncPeriod.String(),
"poll-interval", pollInterval.String(), "poll-jitter", pollJitter, "max-reconcile-rate", *maxReconcileRate)

cfg, err := ctrl.GetConfig()
kingpin.FatalIfError(err, "Cannot get API server rest config")
Expand All @@ -94,9 +108,12 @@ func main() {
metrics.Registry.MustRegister(metricRecorder)
metrics.Registry.MustRegister(stateMetrics)

ctx := context.Background()
provider, err := config.GetProvider(ctx, false)
kingpin.FatalIfError(err, "Cannot initialize the provider configuration")
o := tjcontroller.Options{
Options: xpcontroller.Options{
Logger: log,
Logger: logr,
GlobalRateLimiter: ratelimiter.NewGlobal(*maxReconcileRate),
PollInterval: *pollInterval,
MaxConcurrentReconciles: *maxReconcileRate,
Expand All @@ -107,29 +124,28 @@ func main() {
MRStateMetrics: stateMetrics,
},
},
Provider: config.GetProvider(),
// 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(*terraformVersion, *providerSource, *providerVersion),
Provider: provider,
SetupFn: clients.TerraformSetupBuilder(provider.TerraformProvider),
PollJitter: pollJitter,
OperationTrackerStore: tjcontroller.NewOperationStore(logr),
}

if *enableExternalSecretStores {
o.Features.Enable(features.EnableAlphaExternalSecretStores)
o.SecretStoreConfigGVK = &v1alpha1.StoreConfigGroupVersionKind
log.Info("Alpha feature enabled", "flag", features.EnableAlphaExternalSecretStores)
logr.Info("Alpha feature enabled", "flag", features.EnableAlphaExternalSecretStores)

o.ESSOptions = &tjcontroller.ESSOptions{}
if *essTLSCertsPath != "" {
log.Info("ESS TLS certificates path is set. Loading mTLS configuration.")
logr.Info("ESS TLS certificates path is set. Loading mTLS configuration.")
tCfg, err := certificates.LoadMTLSConfig(filepath.Join(*essTLSCertsPath, "ca.crt"), filepath.Join(*essTLSCertsPath, "tls.crt"), filepath.Join(*essTLSCertsPath, "tls.key"), false)
kingpin.FatalIfError(err, "Cannot load ESS TLS config.")

o.ESSOptions.TLSConfig = tCfg
}

// Ensure default store config exists.
kingpin.FatalIfError(resource.Ignore(kerrors.IsAlreadyExists, mgr.GetClient().Create(context.Background(), &v1alpha1.StoreConfig{
kingpin.FatalIfError(resource.Ignore(kerrors.IsAlreadyExists, mgr.GetClient().Create(ctx, &v1alpha1.StoreConfig{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
},
Expand All @@ -145,7 +161,7 @@ func main() {

if *enableManagementPolicies {
o.Features.Enable(features.EnableBetaManagementPolicies)
log.Info("Beta feature enabled", "flag", features.EnableBetaManagementPolicies)
logr.Info("Beta feature enabled", "flag", features.EnableBetaManagementPolicies)
}

kingpin.FatalIfError(controller.Setup(mgr, o), "Cannot setup AzAPI controllers")
Expand Down
35 changes: 24 additions & 11 deletions config/external_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import "github.com/crossplane/upjet/pkg/config"
// ExternalNameConfigs contains all external name configurations for this
// provider.
var ExternalNameConfigs = map[string]config.ExternalName{

"azapi_data_plane_resource": config.IdentifierFromProvider,
"azapi_resource": config.IdentifierFromProvider,
"azapi_resource_action": config.IdentifierFromProvider,
Expand All @@ -28,15 +27,29 @@ func ExternalNameConfigurations() config.ResourceOption {
}
}

// ExternalNameConfigured returns the list of all resources whose external name
// is configured manually.
func ExternalNameConfigured() []string {
l := make([]string, len(ExternalNameConfigs))
i := 0
for name := range ExternalNameConfigs {
// $ is added to match the exact string since the format is regex.
l[i] = name + "$"
i++
// cliReconciledExternalNameConfigs contains all external name configurations
// belonging to Terraform resources to be reconciled under the CLI-based
// architecture for this provider.
var cliReconciledExternalNameConfigs = map[string]config.ExternalName{}

// resourceConfigurator applies all external name configs
// listed in the table NoForkExternalNameConfigs and
// cliReconciledExternalNameConfigs and sets the version
// of those resources to v1beta1. For those resource in
// noForkExternalNameConfigs, it also sets
// config.Resource.UseNoForkClient to `true`.
func resourceConfigurator() config.ResourceOption {
return func(r *config.Resource) {
// if configured both for the no-fork and CLI based architectures,
// no-fork configuration prevails
e, configured := ExternalNameConfigs[r.Name]
if !configured {
e, configured = cliReconciledExternalNameConfigs[r.Name]
}
if !configured {
return
}
r.Version = "v1beta1"
r.ExternalName = e
}
return l
}
57 changes: 53 additions & 4 deletions config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ Copyright 2021 Upbound Inc.
package config

import (
"context"
// Note(turkenh): we are importing this to embed provider schema document
_ "embed"

"github.com/Azure/terraform-provider-azapi/xpprovider"
ujconfig "github.com/crossplane/upjet/pkg/config"
conversiontfjson "github.com/crossplane/upjet/pkg/types/conversion/tfjson"
tfjson "github.com/hashicorp/terraform-json"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/pkg/errors"
"github.com/upbound/provider-azapi/config/resources"
)

Expand All @@ -23,14 +29,44 @@ var providerSchema string
//go:embed provider-metadata.yaml
var providerMetadata string

func getProviderSchema(s string) (*schema.Provider, error) {
ps := tfjson.ProviderSchemas{}
if err := ps.UnmarshalJSON([]byte(s)); err != nil {
panic(err)
}
if len(ps.Schemas) != 1 {
return nil, errors.Errorf("there should exactly be 1 provider schema but there are %d", len(ps.Schemas))
}
var rs map[string]*tfjson.Schema
for _, v := range ps.Schemas {
rs = v.ResourceSchemas
break
}
return &schema.Provider{
ResourcesMap: conversiontfjson.GetV2ResourceMap(rs),
}, nil
}

// GetProvider returns provider configuration
func GetProvider() *ujconfig.Provider {
func GetProvider(ctx context.Context, generationProvider bool) (*ujconfig.Provider, error) {
var p *schema.Provider
var err error
if generationProvider {
p, err = getProviderSchema(providerSchema)
} else {
p, err = xpprovider.GetProviderSchema(ctx)
}
if err != nil {
return nil, errors.Wrapf(err, "cannot get the Terraform provider schema with generation mode set to %t", generationProvider)
}
pc := ujconfig.NewProvider([]byte(providerSchema), resourcePrefix, modulePath, []byte(providerMetadata),
ujconfig.WithIncludeList(resourceList(cliReconciledExternalNameConfigs)),
ujconfig.WithRootGroup("azapi.upbound.io"),
ujconfig.WithIncludeList(ExternalNameConfigured()),
ujconfig.WithTerraformPluginSDKIncludeList(resourceList(ExternalNameConfigs)),
ujconfig.WithFeaturesPackage("internal/features"),
ujconfig.WithTerraformProvider(p),
ujconfig.WithDefaultResourceOptions(
ExternalNameConfigurations(),
resourceConfigurator(),
))

for _, configure := range []func(provider *ujconfig.Provider){
Expand All @@ -41,5 +77,18 @@ func GetProvider() *ujconfig.Provider {
}

pc.ConfigureResources()
return pc
return pc, nil
}

// resourceList returns the list of resources that have external
// name configured in the specified table.
func resourceList(t map[string]ujconfig.ExternalName) []string {
l := make([]string, len(t))
i := 0
for n := range t {
// Expected format is regex and we'd like to have exact matches.
l[i] = n + "$"
i++
}
return l
}
2 changes: 2 additions & 0 deletions examples/resources/v1beta1/resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ spec:
adminUserEnabled = true
}
})}
identity:
- type: SystemAssigned
location: West Europe
name: registrytestupbound
parentId: ${data.azurerm_resource_group.example.id}
Expand Down
Loading

0 comments on commit b3d1382

Please sign in to comment.