-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create Watchtower Project Resources #1
Changes from all commits
86950be
184b6ff
d970251
cd195d3
60d6736
28fde09
10c267c
16d9281
03b2b99
6e1fa7d
02bc040
3c039d8
dc2ebec
566700f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,34 @@ | ||
module github.com/hashicorp/terraform-provider-scaffolding | ||
module github.com/hashicorp/terraform-provider-watchtower | ||
|
||
go 1.12 | ||
|
||
require github.com/hashicorp/terraform-plugin-sdk v1.4.1 | ||
require ( | ||
cloud.google.com/go/storage v1.8.0 // indirect | ||
github.com/agext/levenshtein v1.2.3 // indirect | ||
github.com/aws/aws-sdk-go v1.31.5 // indirect | ||
github.com/google/go-cmp v0.4.1 // indirect | ||
github.com/hashicorp/go-getter v1.4.1 // indirect | ||
github.com/hashicorp/go-hclog v0.14.0 // indirect | ||
github.com/hashicorp/go-plugin v1.3.0 // indirect | ||
github.com/hashicorp/hcl/v2 v2.5.1 // indirect | ||
github.com/hashicorp/terraform-plugin-sdk v1.13.0 | ||
github.com/hashicorp/terraform-svchost v0.0.0-20191119180714-d2e4933b9136 // indirect | ||
github.com/hashicorp/vault-plugin-secrets-ad v0.6.5 // indirect | ||
github.com/hashicorp/watchtower v0.0.0-20200526204621-44152ae63ead | ||
github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d // indirect | ||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect | ||
github.com/mitchellh/mapstructure v1.3.1 // indirect | ||
github.com/oklog/run v1.1.0 // indirect | ||
github.com/sergi/go-diff v1.1.0 // indirect | ||
github.com/stretchr/testify v1.5.1 | ||
github.com/ulikunitz/xz v0.5.7 // indirect | ||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect | ||
github.com/zclconf/go-cty v1.4.1 // indirect | ||
golang.org/x/mod v0.3.0 // indirect | ||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 // indirect | ||
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect | ||
golang.org/x/tools v0.0.0-20200526205600-253fce384c97 // indirect | ||
google.golang.org/api v0.25.0 // indirect | ||
google.golang.org/genproto v0.0.0-20200526151428-9bb895338b15 // indirect | ||
honnef.co/go/tools v0.0.1-2020.1.4 // indirect | ||
) |
Large diffs are not rendered by default.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,57 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
"github.com/hashicorp/terraform-plugin-sdk/terraform" | ||
"github.com/hashicorp/watchtower/api" | ||
) | ||
|
||
func New() terraform.ResourceProvider { | ||
return &schema.Provider{ | ||
DataSourcesMap: map[string]*schema.Resource{ | ||
"scaffolding_data_source": dataSourceScaffolding(), | ||
p := &schema.Provider{ | ||
Schema: map[string]*schema.Schema{ | ||
"default_organization": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("WATCHTOWER_DEFAULT_ORG", ""), | ||
Description: "The Watchtower organization scope to operate all actions in if not provided in the individual resources.", | ||
}, | ||
"base_url": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "The base url of the Watchtower API. For example 'http://127.0.0.1/'", | ||
}, | ||
}, | ||
ResourcesMap: map[string]*schema.Resource{ | ||
"scaffolding_resource": resourceScaffolding(), | ||
"watchtower_project": resourceProject(), | ||
}, | ||
} | ||
|
||
p.ConfigureFunc = providerConfigure(p) | ||
|
||
return p | ||
} | ||
|
||
type metaData struct { | ||
client *api.Client | ||
ctx context.Context | ||
} | ||
|
||
func providerConfigure(p *schema.Provider) schema.ConfigureFunc { | ||
return func(d *schema.ResourceData) (interface{}, error) { | ||
client, err := api.NewClient(nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := client.SetAddr(d.Get("base_url").(string)); err != nil { | ||
return nil, err | ||
} | ||
client.SetOrg(d.Get("default_organization").(string)) | ||
|
||
// TODO: Pass these in through the config, add token, etc... | ||
client.SetLimiter(5, 5) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to expose the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe at first? I think in the long run we may want to set a reasonable initial default and then allow the controller and go client to handle backoffs and retries. Thoughts? |
||
|
||
return &metaData{client: client, ctx: p.StopContext()}, nil | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package provider | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
"github.com/hashicorp/terraform-plugin-sdk/terraform" | ||
) | ||
|
||
var testProvider *schema.Provider | ||
var testProviders map[string]terraform.ResourceProvider | ||
|
||
func init() { | ||
testProvider = New().(*schema.Provider) | ||
testProviders = map[string]terraform.ResourceProvider{ | ||
"watchtower": testProvider, | ||
} | ||
} | ||
|
||
func TestProvider(t *testing.T) { | ||
if err := New().(*schema.Provider).InternalValidate(); err != nil { | ||
t.Fatalf("err: %s", err) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package provider | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
"github.com/hashicorp/watchtower/api/scopes" | ||
) | ||
|
||
const ( | ||
projectDescriptionKey = "description" | ||
projectNameKey = "name" | ||
) | ||
|
||
func resourceProject() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceProjectCreate, | ||
Read: resourceProjectRead, | ||
Update: resourceProjectUpdate, | ||
Delete: resourceProjectDelete, | ||
|
||
// TODO: Add the ability to define a parent org instead of using one defined in the provider. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment surfaced some interesting thinking about architecture here: do we want resources within an instance of this provider to be able to provision resources within another org not declared outside the provider instance? If we do allow resources to be created within orgs that are not globally defined to the instance of the provider, we'll have to bake in org logic into every resource within the provider. There was a similar issue with Vault's provider and Namespace support. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made this TODO and changed the provider's schema key from "organization" to "default_organization" after a comment from @jefferai where he seemed to hint at the need to do multi org config in a single tf config. The go client does facilitate setting a default org for the whole client and allowing a per call override. I think that would just mean that the resource logic would be checking to see for an org defined at the resource level and if so, using that for the calls through the go client. |
||
Schema: map[string]*schema.Schema{ | ||
projectNameKey: { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
projectDescriptionKey: { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
// convertProjectToResourceData populates the provided ResourceData with the appropriate values from the provided Project. | ||
// The project passed into thie function should be one read from the watchtower API with all fields populated. | ||
func convertProjectToResourceData(p *scopes.Project, d *schema.ResourceData) error { | ||
if p.Name != nil { | ||
if err := d.Set(projectNameKey, p.Name); err != nil { | ||
return err | ||
} | ||
} | ||
if p.Description != nil { | ||
if err := d.Set(projectDescriptionKey, p.Description); err != nil { | ||
return err | ||
} | ||
} | ||
d.SetId(p.Id) | ||
return nil | ||
} | ||
|
||
// convertResourceDataToProject returns a localy built Project using the values provided in the ResourceData. | ||
func convertResourceDataToProject(d *schema.ResourceData) *scopes.Project { | ||
p := &scopes.Project{} | ||
if descVal, ok := d.GetOk(projectDescriptionKey); ok { | ||
desc := descVal.(string) | ||
p.Description = &desc | ||
} | ||
if nameVal, ok := d.GetOk(projectNameKey); ok { | ||
name := nameVal.(string) | ||
p.Name = &name | ||
} | ||
if d.Id() != "" { | ||
p.Id = d.Id() | ||
} | ||
return p | ||
} | ||
|
||
func resourceProjectCreate(d *schema.ResourceData, meta interface{}) error { | ||
md := meta.(*metaData) | ||
client := md.client | ||
ctx := md.ctx | ||
|
||
// The org id is declared in the client, so no need to specify that here. | ||
o := &scopes.Organization{ | ||
Client: client, | ||
} | ||
p := convertResourceDataToProject(d) | ||
p, _, err := o.CreateProject(ctx, p) | ||
if err != nil { | ||
return err | ||
} | ||
d.SetId(p.Id) | ||
|
||
return nil | ||
} | ||
|
||
func resourceProjectRead(d *schema.ResourceData, meta interface{}) error { | ||
md := meta.(*metaData) | ||
client := md.client | ||
ctx := md.ctx | ||
|
||
o := &scopes.Organization{ | ||
Client: client, | ||
} | ||
p := &scopes.Project{Id: d.Id()} | ||
p, _, err := o.ReadProject(ctx, p) | ||
if err != nil { | ||
return err | ||
} | ||
return convertProjectToResourceData(p, d) | ||
} | ||
|
||
func resourceProjectUpdate(d *schema.ResourceData, meta interface{}) error { | ||
md := meta.(*metaData) | ||
client := md.client | ||
ctx := md.ctx | ||
|
||
o := &scopes.Organization{ | ||
Client: client, | ||
} | ||
p := &scopes.Project{ | ||
Id: d.Id(), | ||
} | ||
|
||
if d.HasChange(projectDescriptionKey) { | ||
desc := d.Get(projectDescriptionKey).(string) | ||
if desc == "" { | ||
p.SetDefault(projectDescriptionKey) | ||
} else { | ||
p.Description = &desc | ||
} | ||
} | ||
|
||
if d.HasChange(projectNameKey) { | ||
name := d.Get(projectNameKey).(string) | ||
if name == "" { | ||
p.SetDefault(projectNameKey) | ||
} else { | ||
p.Name = &name | ||
} | ||
} | ||
|
||
p, _, err := o.UpdateProject(ctx, p) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return convertProjectToResourceData(p, d) | ||
} | ||
|
||
func resourceProjectDelete(d *schema.ResourceData, meta interface{}) error { | ||
md := meta.(*metaData) | ||
client := md.client | ||
ctx := md.ctx | ||
|
||
o := &scopes.Organization{ | ||
Client: client, | ||
} | ||
p := convertResourceDataToProject(d) | ||
_, _, err := o.DeleteProject(ctx, p) | ||
if err != nil { | ||
return fmt.Errorf("failed deleting project: %w", err) | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the provider is called
watchtower
I vote to have the key here be justproject
so it doesn't duplicate the primary namespace of the provider.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to make the change. I thought that since different providers can be included in a single tf config the resources needed to be named in a way that won't collide with resources from other providers. For example, all github related resources have a "github_" prefix and the google related resources have "google_" prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, I agree with your assessment. This makes sense as-is, let's leave it.