Last active
July 8, 2024 08:47
-
-
Save Praveen005/eabe81a92b6d679172701fe030f667b4 to your computer and use it in GitHub Desktop.
Feature requested in #246 for terraform-provider-civo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package civo | |
import ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"os" | |
"path/filepath" | |
"github.com/civo/civogo" | |
"github.com/civo/terraform-provider-civo/civo/database" | |
"github.com/civo/terraform-provider-civo/civo/disk" | |
"github.com/civo/terraform-provider-civo/civo/dns" | |
"github.com/civo/terraform-provider-civo/civo/firewall" | |
"github.com/civo/terraform-provider-civo/civo/instances" | |
"github.com/civo/terraform-provider-civo/civo/ip" | |
"github.com/civo/terraform-provider-civo/civo/kubernetes" | |
"github.com/civo/terraform-provider-civo/civo/loadbalancer" | |
"github.com/civo/terraform-provider-civo/civo/network" | |
"github.com/civo/terraform-provider-civo/civo/objectstorage" | |
"github.com/civo/terraform-provider-civo/civo/region" | |
"github.com/civo/terraform-provider-civo/civo/size" | |
"github.com/civo/terraform-provider-civo/civo/ssh" | |
"github.com/civo/terraform-provider-civo/civo/volume" | |
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | |
) | |
var ( | |
// ProviderVersion is the version of the provider to set in the User-Agent header | |
ProviderVersion = "dev" | |
// ProdAPI is the Base URL for CIVO Production API | |
ProdAPI = "https://api.civo.com" | |
) | |
// Provider Civo cloud provider | |
func Provider() *schema.Provider { | |
return &schema.Provider{ | |
Schema: map[string]*schema.Schema{ | |
//------------change 1: replaced civo_token with credential_file field----------------- | |
"credential_file": { | |
Type: schema.TypeString, | |
Optional: true, | |
DefaultFunc: schema.EnvDefaultFunc("CIVO_CREDENTIAL_FILE", ""), | |
Description: "Path to the Civo credentials file. Can be specified using CIVO_CREDENTIAL_FILE environment variable.", | |
}, | |
//------------change 1 ends-------------------------------------------------------------- | |
"region": { | |
Type: schema.TypeString, | |
Optional: true, | |
DefaultFunc: schema.EnvDefaultFunc("CIVO_REGION", ""), | |
Description: "If region is not set, then no region will be used and them you need expensify in every resource even if you expensify here you can overwrite in a resource.", | |
}, | |
"api_endpoint": { | |
Type: schema.TypeString, | |
Optional: true, | |
DefaultFunc: schema.EnvDefaultFunc("CIVO_API_URL", ProdAPI), | |
Description: "The Base URL to use for CIVO API.", | |
}, | |
}, | |
DataSourcesMap: map[string]*schema.Resource{ | |
// "civo_template": dataSourceTemplate(), | |
"civo_disk_image": disk.DataSourceDiskImage(), | |
"civo_kubernetes_version": kubernetes.DataSourceKubernetesVersion(), | |
"civo_kubernetes_cluster": kubernetes.DataSourceKubernetesCluster(), | |
"civo_size": size.DataSourceSize(), | |
"civo_instances": instances.DataSourceInstances(), | |
"civo_instance": instances.DataSourceInstance(), | |
"civo_dns_domain_name": dns.DataSourceDNSDomainName(), | |
"civo_dns_domain_record": dns.DataSourceDNSDomainRecord(), | |
"civo_network": network.DataSourceNetwork(), | |
"civo_volume": volume.DataSourceVolume(), | |
"civo_firewall": firewall.DataSourceFirewall(), | |
"civo_loadbalancer": loadbalancer.DataSourceLoadBalancer(), | |
"civo_ssh_key": ssh.DataSourceSSHKey(), | |
"civo_object_store": objectstorage.DataSourceObjectStore(), | |
"civo_object_store_credential": objectstorage.DataSourceObjectStoreCredential(), | |
"civo_region": region.DataSourceRegion(), | |
"civo_reserved_ip": ip.DataSourceReservedIP(), | |
"civo_database": database.DataSourceDatabase(), | |
"civo_database_version": database.DataDatabaseVersion(), | |
}, | |
ResourcesMap: map[string]*schema.Resource{ | |
"civo_instance": instances.ResourceInstance(), | |
"civo_instance_reserved_ip_assignment": instances.ResourceInstanceReservedIPAssignment(), | |
"civo_network": network.ResourceNetwork(), | |
"civo_volume": volume.ResourceVolume(), | |
"civo_volume_attachment": volume.ResourceVolumeAttachment(), | |
"civo_dns_domain_name": dns.ResourceDNSDomainName(), | |
"civo_dns_domain_record": dns.ResourceDNSDomainRecord(), | |
"civo_firewall": firewall.ResourceFirewall(), | |
"civo_ssh_key": ssh.ResourceSSHKey(), | |
"civo_kubernetes_cluster": kubernetes.ResourceKubernetesCluster(), | |
"civo_kubernetes_node_pool": kubernetes.ResourceKubernetesClusterNodePool(), | |
"civo_reserved_ip": ip.ResourceReservedIP(), | |
"civo_object_store": objectstorage.ResourceObjectStore(), | |
"civo_object_store_credential": objectstorage.ResourceObjectStoreCredential(), | |
"civo_database": database.ResourceDatabase(), | |
}, | |
ConfigureFunc: providerConfigure, | |
} | |
} | |
// Provider configuration | |
func providerConfigure(d *schema.ResourceData) (interface{}, error) { | |
var regionValue, tokenValue, apiURL string | |
var client *civogo.Client | |
var err error | |
if region, ok := d.GetOk("region"); ok { | |
regionValue = region.(string) | |
} | |
//---------change 2: calling getToken() to retrieve token------------------------- | |
if token, ok := getToken(d); ok { | |
tokenValue = token.(string) | |
} else { | |
return nil, fmt.Errorf("[ERR] token not found") | |
} | |
//--------------------------chnage 2 ends------------------------------------------- | |
if apiEndpoint, ok := d.GetOk("api_endpoint"); ok { | |
apiURL = apiEndpoint.(string) | |
} else { | |
apiURL = ProdAPI | |
} | |
client, err = civogo.NewClientWithURL(tokenValue, apiURL, regionValue) | |
if err != nil { | |
return nil, err | |
} | |
userAgent := &civogo.Component{ | |
Name: "terraform-provider-civo", | |
Version: ProviderVersion, | |
} | |
client.SetUserAgent(userAgent) | |
log.Printf("[DEBUG] Civo API URL: %s\n", apiURL) | |
return client, nil | |
} | |
//-------------change 3: getToken() function definition and other helper functions--------------------------------- | |
func getToken(d *schema.ResourceData) (interface{}, bool) { | |
var exists = true | |
// Check for CIVO_TOKEN environment variable | |
if token := os.Getenv("CIVO_TOKEN"); token != "" { | |
return token, exists | |
} | |
// Check for credentials file specified in provider config | |
if credFile, ok := d.GetOk("credential_file"); ok { | |
token, err := readTokenFromFile(credFile.(string)) | |
if err == nil { | |
return token, exists | |
} | |
} | |
// Check for default CLI config file | |
homeDir, err := getHomeDir() | |
if err == nil { | |
token, err := readTokenFromFile(filepath.Join(homeDir, ".civo.json")) | |
if err == nil { | |
return token, exists | |
} | |
} | |
return nil, !exists | |
} | |
var getHomeDir = func() (string, error) { | |
if home := os.Getenv("HOME"); home != "" { | |
return home, nil | |
} | |
// Fall back to os.UserHomeDir() if HOME is not set | |
return os.UserHomeDir() | |
} | |
func readTokenFromFile(path string) (string, error) { | |
data, err := os.ReadFile(path) | |
if err != nil { | |
return "", err | |
} | |
var config struct { | |
APIKeys struct { | |
Token string `json:"CIVO_TOKEN"` | |
} `json:"apikeys"` | |
} | |
if err := json.Unmarshal(data, &config); err != nil { | |
return "", err | |
} | |
return config.APIKeys.Token, nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Set the variable value in *.tfvars file or using -var="credential_file=..." CLI flag | |
#------change 1---------------- | |
variable "credential_file" {} | |
#------change 1 ends---------------- | |
# Specify required provider as maintained by civo | |
terraform { | |
required_providers { | |
civo = { | |
source = "civo/civo" | |
} | |
} | |
} | |
# Configure the Civo Provider | |
provider "civo" { | |
#------change 2---------------- | |
credential_file = var.credential_file | |
#------change 2 ends---------------- | |
region = "LON1" | |
} | |
# Create a web server | |
resource "civo_instance" "web" { | |
# ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package civo | |
import ( | |
"context" | |
"fmt" | |
"os" | |
"path/filepath" | |
"strings" | |
"testing" | |
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | |
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | |
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform" | |
) | |
// TestProvider tests the provider configuration | |
func TestProvider(t *testing.T) { | |
if err := Provider().InternalValidate(); err != nil { | |
t.Fatalf("err: %s", err.Error()) | |
} | |
} | |
// TestProvider_impl tests the provider implementation | |
func TestProvider_impl(t *testing.T) { | |
var _ *schema.Provider = Provider() | |
} | |
// TestToken tests the token configuration | |
func TestToken(t *testing.T) { | |
t.Run("reading token from environment variable", func(t *testing.T) { | |
const testToken = "env12345" | |
oldToken := os.Getenv("CIVO_TOKEN") | |
os.Setenv("CIVO_TOKEN", testToken) | |
defer os.Setenv("CIVO_TOKEN", oldToken) // Restore the original value | |
raw := map[string]interface{}{} | |
configureProvider(t, raw) | |
}) | |
t.Run("reading token from credential file", func(t *testing.T) { | |
tempDir, err := os.MkdirTemp("", "civo-provider-test") | |
if err != nil { | |
t.Fatalf("Failed to create temp dir: %v", err) | |
} | |
defer os.RemoveAll(tempDir) | |
credentialFile := filepath.Join(tempDir, "credential.json") | |
testToken := "file3409" | |
credContent := fmt.Sprintf(`{"apikeys":{"CIVO_TOKEN":"%s"}}`, testToken) | |
err = os.WriteFile(credentialFile, []byte(credContent), 0600) | |
if err != nil { | |
t.Fatalf("Failed to write credentials file: %v", err) | |
} | |
raw := map[string]interface{}{ | |
"credential_file": credentialFile, | |
} | |
configureProvider(t, raw) | |
}) | |
t.Run("reading token from CLI config", func(t *testing.T) { | |
// Create a mock CLI config file | |
tempDir, err := os.MkdirTemp("", "civo-cli-config-test") | |
if err != nil { | |
t.Fatalf("Failed to create temp dir: %v", err) | |
} | |
defer os.RemoveAll(tempDir) | |
testToken := "12345" | |
cliConfigContent := fmt.Sprintf(` | |
{ | |
"apikeys": { | |
"CIVO_TOKEN": "%s" | |
}, | |
"meta": { | |
"admin": false, | |
"current_apikey": "CIVO_TOKEN", | |
"default_region": "NYC1", | |
"latest_release_check": "2024-07-07T14:07:51.996201195+05:30", | |
"url": "https://api.civo.com", | |
"last_command_executed": "2024-06-20T15:09:00.212548723+05:30" | |
}, | |
"region_to_features": null | |
}`, testToken) | |
cliConfigFile := filepath.Join(tempDir, ".civo.json") | |
err = os.WriteFile(cliConfigFile, []byte(cliConfigContent), 0600) | |
if err != nil { | |
t.Fatalf("Failed to write CLI config file: %v", err) | |
} | |
// Temporarily set HOME to our temp directory | |
oldHome := os.Getenv("HOME") | |
os.Setenv("HOME", tempDir) | |
defer os.Setenv("HOME", oldHome) | |
raw := map[string]interface{}{} | |
configureProvider(t, raw) | |
}) | |
} | |
func diagnosticsToString(diags diag.Diagnostics) string { | |
diagsAsStrings := make([]string, len(diags)) | |
for i, diag := range diags { | |
diagsAsStrings[i] = diag.Summary | |
} | |
return strings.Join(diagsAsStrings, "; ") | |
} | |
func configureProvider(t testing.TB, raw map[string]interface{}) { | |
t.Helper() | |
rawProvider := Provider() | |
diags := rawProvider.Configure(context.Background(), terraform.NewResourceConfigRaw(raw)) | |
if diags.HasError() { | |
t.Fatalf("provider configure failed: %s", diagnosticsToString(diags)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment