Skip to content

Commit

Permalink
terraform/exec/plugins: add embedded 'azureprivatedns' provider to ha…
Browse files Browse the repository at this point in the history
…ndle private_dns zone

Using the upstream azurerm provider is not possible for now because of following reasons:

1) There is not srv record resource for private dns zone

2) The version of provider that has the private dns zone resources `1.34.0` has a lot of bugs like
    * hashicorp/terraform-provider-azurerm#4452
    * hashicorp/terraform-provider-azurerm#4453
    * hashicorp/terraform-provider-azurerm#4501
    Some of these bugs are fixed, and some are in flight.

    Another reason moving to `1.36.0` which might have all the fixes we need is the provider has moved to using
    `standalone terraform plugin SDK v1.1.1` [1]. Because we vendor both terraform and providers, this causes errors like
    `panic: gob: registering duplicate types for "github.com/zclconf/go-cty/cty.primitiveType": cty.primitiveType != cty.primitiveType`

   Therefore, we would have to move towards a single vendor for terraform and plugins for correct inter-operation, which is tricker due to conflicts elsewhere

A simple 4 resource plugin that re-uses the already vendored azurerm provider as library and carries over the required resources seems like an easy fix for now.

[1]: hashicorp/terraform-provider-azurerm#4474
  • Loading branch information
abhinavdahiya committed Oct 8, 2019
1 parent ba00702 commit af00810
Show file tree
Hide file tree
Showing 15 changed files with 4,327 additions and 1 deletion.
15 changes: 14 additions & 1 deletion pkg/terraform/exec/plugins/Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions pkg/terraform/exec/plugins/azureprivatedns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package plugins

import (
"github.com/hashicorp/terraform/plugin"
"github.com/openshift/installer/pkg/terraform/exec/plugins/azureprivatedns"
)

func init() {
azurePrivateDNSProvider := func() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: azureprivatedns.Provider,
})
}
KnownPlugins["terraform-provider-azureprivatedns"] = azurePrivateDNSProvider
}
114 changes: 114 additions & 0 deletions pkg/terraform/exec/plugins/azureprivatedns/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package azureprivatedns

import (
"context"
"fmt"
"log"
"os"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
az "github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/go-azure-helpers/authentication"
"github.com/hashicorp/terraform/httpclient"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/version"
)

// ArmClient contains the handles to all the specific Azure Resource Manager
// resource classes' respective clients.
type ArmClient struct {
clientID string
tenantID string
subscriptionID string
usingServicePrincipal bool
environment az.Environment

StopContext context.Context

recordSetsClient *privatedns.RecordSetsClient
privateZonesClient *privatedns.PrivateZonesClient
virtualNetworkLinksClient *privatedns.VirtualNetworkLinksClient
}

func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) {
setUserAgent(client)
client.Authorizer = auth
client.Sender = azure.BuildSender()
client.SkipResourceProviderRegistration = true
client.PollingDuration = 60 * time.Minute
}

func setUserAgent(client *autorest.Client) {
// TODO: This is the SDK version not the CLI version, once we are on 0.12, should revisit
tfUserAgent := httpclient.UserAgentString()

pv := version.ProviderVersion
providerUserAgent := fmt.Sprintf("%s terraform-provider-azurerm/%s", tfUserAgent, pv)
client.UserAgent = strings.TrimSpace(fmt.Sprintf("%s %s", client.UserAgent, providerUserAgent))

// append the CloudShell version to the user agent if it exists
if azureAgent := os.Getenv("AZURE_HTTP_USER_AGENT"); azureAgent != "" {
client.UserAgent = fmt.Sprintf("%s %s", client.UserAgent, azureAgent)
}

log.Printf("[DEBUG] AzureRM Client User Agent: %s\n", client.UserAgent)
}

// getArmClient is a helper method which returns a fully instantiated
// *ArmClient based on the Config's current settings.
func getArmClient(c *authentication.Config) (*ArmClient, error) {
env, err := authentication.DetermineEnvironment(c.Environment)
if err != nil {
return nil, err
}

// client declarations:
client := ArmClient{
clientID: c.ClientID,
tenantID: c.TenantID,
subscriptionID: c.SubscriptionID,
environment: *env,
usingServicePrincipal: c.AuthenticatedAsAServicePrincipal,
}

oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, c.TenantID)
if err != nil {
return nil, err
}

// OAuthConfigForTenant returns a pointer, which can be nil.
if oauthConfig == nil {
return nil, fmt.Errorf("unable to configure OAuthConfig for tenant %s", c.TenantID)
}

sender := azure.BuildSender()

// Resource Manager endpoints
endpoint := env.ResourceManagerEndpoint
auth, err := c.GetAuthorizationToken(sender, oauthConfig, env.TokenAudience)
if err != nil {
return nil, err
}

client.registerPrivateDNSClients(endpoint, c.SubscriptionID, auth)

return &client, nil
}

func (c *ArmClient) registerPrivateDNSClients(endpoint, subscriptionID string, auth autorest.Authorizer) {
rs := privatedns.NewRecordSetsClientWithBaseURI(endpoint, subscriptionID)
c.configureClient(&rs.Client, auth)
c.recordSetsClient = &rs

zo := privatedns.NewPrivateZonesClientWithBaseURI(endpoint, subscriptionID)
c.configureClient(&zo.Client, auth)
c.privateZonesClient = &zo

vnl := privatedns.NewVirtualNetworkLinksClientWithBaseURI(endpoint, subscriptionID)
c.configureClient(&vnl.Client, auth)
c.virtualNetworkLinksClient = &vnl
}
157 changes: 157 additions & 0 deletions pkg/terraform/exec/plugins/azureprivatedns/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package azureprivatedns

import (
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"fmt"
"strings"

"github.com/hashicorp/go-azure-helpers/authentication"
"github.com/hashicorp/terraform/helper/mutexkv"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress"
)

// Provider returns a terraform.ResourceProvider.
func Provider() terraform.ResourceProvider {
p := &schema.Provider{
Schema: map[string]*schema.Schema{
"subscription_id": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
},

"client_id": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
},

"tenant_id": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
},

"environment": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_ENVIRONMENT", "public"),
},

// Client Secret specific fields
"client_secret": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
},
},

DataSourcesMap: map[string]*schema.Resource{},

ResourcesMap: map[string]*schema.Resource{
"azureprivatedns_zone": resourceArmPrivateDNSZone(),
"azureprivatedns_a_record": resourceArmPrivateDNSARecord(),
"azureprivatedns_srv_record": resourceArmPrivateDNSSrvRecord(),
"azureprivatedns_zone_virtual_network_link": resourceArmPrivateDNSZoneVirtualNetworkLink(),
},
}

p.ConfigureFunc = providerConfigure(p)

return p
}

func providerConfigure(p *schema.Provider) schema.ConfigureFunc {
return func(d *schema.ResourceData) (interface{}, error) {
builder := &authentication.Builder{
SubscriptionID: d.Get("subscription_id").(string),
ClientID: d.Get("client_id").(string),
ClientSecret: d.Get("client_secret").(string),
TenantID: d.Get("tenant_id").(string),
Environment: d.Get("environment").(string),

// Feature Toggles
SupportsClientSecretAuth: true,
}

config, err := builder.Build()
if err != nil {
return nil, fmt.Errorf("error building AzureRM Client: %s", err)
}

client, err := getArmClient(config)

if err != nil {
return nil, err
}

client.StopContext = p.StopContext()

// replaces the context between tests
p.MetaReset = func() error {
client.StopContext = p.StopContext()
return nil
}

return client, nil
}
}

// armMutexKV is the instance of MutexKV for ARM resources
var armMutexKV = mutexkv.NewMutexKV()

// Deprecated: use `suppress.CaseDifference` instead
func ignoreCaseDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool {
return suppress.CaseDifference(k, old, new, d)
}

// ignoreCaseStateFunc is a StateFunc from helper/schema that converts the
// supplied value to lower before saving to state for consistency.
func ignoreCaseStateFunc(val interface{}) string {
return strings.ToLower(val.(string))
}

func userDataDiffSuppressFunc(_, old, new string, _ *schema.ResourceData) bool {
return userDataStateFunc(old) == new
}

func userDataStateFunc(v interface{}) string {
switch s := v.(type) {
case string:
s = base64Encode(s)
hash := sha1.Sum([]byte(s))
return hex.EncodeToString(hash[:])
default:
return ""
}
}

func base64EncodedStateFunc(v interface{}) string {
switch s := v.(type) {
case string:
return base64Encode(s)
default:
return ""
}
}

// base64Encode encodes data if the input isn't already encoded using
// base64.StdEncoding.EncodeToString. If the input is already base64 encoded,
// return the original input unchanged.
func base64Encode(data string) string {
// Check whether the data is already Base64 encoded; don't double-encode
if isBase64Encoded(data) {
return data
}
// data has not been encoded encode and return
return base64.StdEncoding.EncodeToString([]byte(data))
}

func isBase64Encoded(data string) bool {
_, err := base64.StdEncoding.DecodeString(data)
return err == nil
}
Loading

0 comments on commit af00810

Please sign in to comment.