Skip to content

Commit

Permalink
feat(config): added support for .humctl file
Browse files Browse the repository at this point in the history
  • Loading branch information
dharsanb committed May 8, 2024
1 parent 0a6ea3e commit cd14366
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 21 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
github.com/humanitec/humanitec-go-autogen v0.0.0-20240429100802-283cee98d746
github.com/stretchr/testify v1.9.0
sigs.k8s.io/yaml v1.4.0
)

require (
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand Down Expand Up @@ -300,3 +301,5 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
122 changes: 106 additions & 16 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"context"
"crypto/tls"
"errors"
"net/http"
"os"

Expand All @@ -12,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/humanitec/humanitec-go-autogen"
"sigs.k8s.io/yaml"
)

// Ensure HumanitecProvider satisfies various provider interfaces.
Expand All @@ -27,9 +29,11 @@ type HumanitecProvider struct {

// HumanitecProviderModel describes the provider data model.
type HumanitecProviderModel struct {
Host types.String `tfsdk:"host"`
OrgID types.String `tfsdk:"org_id"`
Token types.String `tfsdk:"token"`
APIPrefix types.String `tfsdk:"api_prefix"`
Host types.String `tfsdk:"host"`
OrgID types.String `tfsdk:"org_id"`
Token types.String `tfsdk:"token"`
Config types.String `tfsdk:"config"`

DisableSSLCertificateVerification types.Bool `tfsdk:"disable_ssl_certificate_verification"`
}
Expand All @@ -51,9 +55,14 @@ func (p *HumanitecProvider) Schema(ctx context.Context, req provider.SchemaReque
MarkdownDescription: "Terraform Provider for [Humanitec](https://humanitec.com/).",

Attributes: map[string]schema.Attribute{
"api_prefix": schema.StringAttribute{
MarkdownDescription: "Humanitec API prefix (or using the `HUMANITEC_API_PREFIX` environment variable)",
Optional: true,
},
"host": schema.StringAttribute{
MarkdownDescription: "Humanitec API host (or using the `HUMANITEC_HOST` environment variable)",
Optional: true,
DeprecationMessage: "This attribute is deprecated in favor of api_prefix (`HUMANITEC_API_PREFIX` environment variable).",
},
"org_id": schema.StringAttribute{
MarkdownDescription: "Humanitec Organization ID (or using the `HUMANITEC_ORG` environment variable)",
Expand All @@ -68,20 +77,15 @@ func (p *HumanitecProvider) Schema(ctx context.Context, req provider.SchemaReque
MarkdownDescription: "Disables SSL certificate verification",
Optional: true,
},
"config": schema.StringAttribute{
MarkdownDescription: "Location of Humanitec configuration",
Optional: true,
},
},
}
}

func (p *HumanitecProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
// Check environment variables
host := os.Getenv("HUMANITEC_HOST")
if host == "" {
host = humanitec.DefaultAPIHost
}

orgID := os.Getenv("HUMANITEC_ORG")
token := os.Getenv("HUMANITEC_TOKEN")

var data HumanitecProviderModel

resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
Expand All @@ -90,14 +94,100 @@ func (p *HumanitecProvider) Configure(ctx context.Context, req provider.Configur
return
}

var c Config

// Check for .humctl file generated by humctl command line tool
configFilePath := data.Config.ValueString()
if configFilePath == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
resp.Diagnostics.AddWarning(
"Unable to determine home directory",
"While configuring the provider, terraform was unable "+
"to determine user's home directory to read config file.",
)
configFilePath = ""
} else {
configFilePath = homeDir + "/.humctl"
if _, err := os.Stat(configFilePath); errors.Is(err, os.ErrNotExist) {
configFilePath = ""
}
}

} else if _, err := os.Stat(configFilePath); errors.Is(err, os.ErrNotExist) {
resp.Diagnostics.AddError(
"Unable to read config file",
"Terraform was unable to read config file mentioned "+
"in the config attribute.",
)
// Not returning early allows the logic to collect all errors.
configFilePath = ""
}

if configFilePath != "" {
file, err := os.ReadFile(configFilePath)
if err != nil {
resp.Diagnostics.AddError(
"Unable to read config file",
"Terraform was unable to read the yaml config file "+
"in "+configFilePath,
)
// Not returning early allows the logic to collect all errors.
}

err = yaml.Unmarshal(file, &c)
if err != nil {
resp.Diagnostics.AddError(
"Unable to parse yaml from config file",
"Terraform was unable to parse yaml config "+
"file in "+configFilePath,
)
// Not returning early allows the logic to collect all errors.
}
}
apiPrefix := c.ApiPrefix
orgID := c.Org
token := c.Token

// Environment variables have precedence over config file, if found
if os.Getenv("HUMANITEC_API_PREFIX") != "" {
apiPrefix = os.Getenv("HUMANITEC_API_PREFIX")
} else {
if hostOld := os.Getenv("HUMANITEC_HOST"); hostOld != "" {
apiPrefix = hostOld
resp.Diagnostics.AddWarning(
"Environment variable HUMANITEC_HOST has been deprecated",
"Environment variable HUMANITEC_HOST has been deprecated "+
"please use HUMANITEC_API_PREFIX instead to set your api prefix to the terraform driver.")
} else {
apiPrefix = humanitec.DefaultAPIHost
}
}

if os.Getenv("HUMANITEC_ORG") != "" {
orgID = os.Getenv("HUMANITEC_ORG")
}

if os.Getenv("HUMANITEC_TOKEN") != "" {
token = os.Getenv("HUMANITEC_TOKEN")
}

// Check configuration data, which should take precedence over
// environment variable data, if found.
if data.Host.ValueString() != "" {
host = data.Host.ValueString()
// environment variable data and config file, if found.
if data.APIPrefix.ValueString() != "" {
apiPrefix = data.APIPrefix.ValueString()
} else if data.Host.ValueString() != "" {
apiPrefix = data.Host.ValueString()
resp.Diagnostics.AddWarning(
"Attribute host has been deprecated",
"Attribute hostT has been deprecated "+
"please use api_prefix instead to set your api prefix to the terraform driver.")
}

if data.OrgID.ValueString() != "" {
orgID = data.OrgID.ValueString()
}

if data.Token.ValueString() != "" {
token = data.Token.ValueString()
}
Expand Down Expand Up @@ -132,7 +222,7 @@ func (p *HumanitecProvider) Configure(ctx context.Context, req provider.Configur
doer = &http.Client{}
}

client, err := NewHumanitecClient(host, token, p.version, doer)
client, err := NewHumanitecClient(apiPrefix, token, p.version, doer)
if err != nil {
resp.Diagnostics.AddError("Unable to create Humanitec client", err.Error())
}
Expand Down
10 changes: 5 additions & 5 deletions internal/provider/users_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ func NewUsersDataSource() datasource.DataSource {
return &UsersDataSource{}
}

// SourceIPRangesDataSource defines the data source implementation.
// UsersDataSource defines the data source implementation.
type UsersDataSource struct {
client *humanitec.Client
orgId string
}

// SourceIPRangesDataSourceModel describes the data source data model.
// UsersDataSourceModel describes the data source data model.
type UsersDataSourceModel struct {
ID types.String `tfsdk:"id"`
ID types.String `tfsdk:"id"`
Filter types.Object `tfsdk:"filter"`
Users types.List `tfsdk:"users"`
}
Expand Down Expand Up @@ -173,7 +173,7 @@ func matchesFilters(ctx context.Context, filter basetypes.ObjectValue, userRole
diags := filter.As(ctx, &parsedFilter, basetypes.ObjectAsOptions{})
if len(diags) != 0 {
return false, diags
}
}

id = parsedFilter.Id.ValueStringPointer()
name = parsedFilter.Name.ValueStringPointer()
Expand All @@ -185,4 +185,4 @@ func matchesFilters(ctx context.Context, filter basetypes.ObjectValue, userRole
matchesEmailIfSet := email == nil || (userRole.Email != nil && *userRole.Email == *email)

return matchesIdIfSet && matchesNameIfSet && matchesEmailIfSet, diag.Diagnostics{}
}
}
6 changes: 6 additions & 0 deletions internal/provider/utils.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package provider

type Config struct {
ApiPrefix string `json:"apiPrefix,omitempty"`
Org string `json:"org,omitempty"`
Token string `json:"token,omitempty"`
}

func valueAtPath[T any](input map[string]interface{}, path []string) (T, bool) {
lenPath := len(path)

Expand Down

0 comments on commit cd14366

Please sign in to comment.