Skip to content

Commit 2ef9692

Browse files
authored
Merge pull request #256 from 1Password/andi_t/introduce_terraform_plugin
Add Terraform plugin
2 parents ffbde50 + fb2d5ce commit 2ef9692

5 files changed

Lines changed: 159 additions & 14 deletions

File tree

plugins/terraform/plugin.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package terraform
2+
3+
import (
4+
"github.com/1Password/shell-plugins/sdk"
5+
"github.com/1Password/shell-plugins/sdk/schema"
6+
)
7+
8+
func New() schema.Plugin {
9+
return schema.Plugin{
10+
Name: "terraform",
11+
Platform: schema.PlatformInfo{
12+
Name: "Terraform",
13+
Homepage: sdk.URL("https://www.terraform.io"),
14+
},
15+
Executables: []schema.Executable{
16+
TerraformCLI(),
17+
},
18+
}
19+
}

plugins/terraform/terraform.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package terraform
2+
3+
import (
4+
"github.com/1Password/shell-plugins/sdk"
5+
"github.com/1Password/shell-plugins/sdk/needsauth"
6+
"github.com/1Password/shell-plugins/sdk/schema"
7+
)
8+
9+
func TerraformCLI() schema.Executable {
10+
return schema.Executable{
11+
Name: "Terraform CLI",
12+
Runs: []string{"terraform"},
13+
DocsURL: sdk.URL("https://developer.hashicorp.com/terraform/cli"),
14+
NeedsAuth: needsauth.IfAll(
15+
needsauth.NotForHelpOrVersion(),
16+
needsauth.NotWithoutArgs(),
17+
),
18+
Uses: []schema.CredentialUsage{
19+
{
20+
Description: "Credentials to use within the Terraform project",
21+
SelectFrom: &schema.CredentialSelection{
22+
ID: "project",
23+
IncludeAllCredentials: true,
24+
AllowMultiple: true,
25+
},
26+
Optional: true,
27+
NeedsAuth: needsauth.IfAny(
28+
needsauth.ForCommand("refresh"),
29+
needsauth.ForCommand("plan"),
30+
needsauth.ForCommand("apply"),
31+
needsauth.ForCommand("destroy"),
32+
needsauth.ForCommand("import"),
33+
needsauth.ForCommand("test"),
34+
),
35+
},
36+
},
37+
}
38+
}

sdk/schema/executable.go

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,45 @@ type Executable struct {
2121
// (Optional) A URL to the documentation about this executable.
2222
DocsURL *url.URL
2323

24-
// (Optional) Whether the exectuable needs authentication for certain args.
24+
// (Optional) Whether the executable needs authentication for certain args.
2525
NeedsAuth sdk.NeedsAuthentication
2626
}
2727

2828
type CredentialUsage struct {
29-
// The name of the credential to use in the executable.
29+
// (Optional) The name of the credential to use in the executable. Mutually exclusive with `SelectFrom`.
3030
Name sdk.CredentialName
3131

3232
// (Optional) The plugin name that contains the credential. Defaults to the current package. This can be used to
33-
// include credentials from other plugins.
33+
// include credentials from other plugins. Mutually exclusive with `SelectFrom`.
3434
Plugin string
3535

3636
// (Optional) The provisioner to use to provision this credential to the executable. Overrides the DefaultProvisioner
3737
// set in the credential schema, so should only be used if this executable requires a custom configuration, that deviates
38-
// from the way the credential is usually provisioned.
38+
// from the way the credential is usually provisioned. Mutually exclusive with `SelectFrom`.
3939
Provisioner sdk.Provisioner
40+
41+
// (Optional) What this credential will be used for by the executable.
42+
Description string
43+
44+
// (Optional) Instead of requiring a specific credential, have the user select from a list of compatible credentials.
45+
// Mutually exclusive with: `Name` and `Plugin`.
46+
SelectFrom *CredentialSelection
47+
48+
// (Optional) Whether the exectuable needs authentication for this credential. Works side by side with the executable's
49+
// `NeedsAuth`, which can still be used for more generic authentications opt-outs, such as the help flag.
50+
NeedsAuth sdk.NeedsAuthentication
51+
52+
// Whether this credential is needed for the executable to run. If set to true, the executable cannot run without provisioning this credential.
53+
Optional bool
54+
}
55+
56+
type CredentialSelection struct {
57+
// ID helps identify credentials chosen in this selection. This must be unique in relation to other selections specified in usages within its executable.
58+
ID string
59+
// IncludeAllCredentials specifies whether this selection should contain all credentials defined in all plugins.
60+
IncludeAllCredentials bool
61+
// AllowMultiple specifies whether multiple credentials can be selected to be part of this credential use.
62+
AllowMultiple bool
4063
}
4164

4265
func (e Executable) Validate() (bool, ValidationReport) {
@@ -75,9 +98,57 @@ func (e Executable) Validate() (bool, ValidationReport) {
7598
Severity: ValidationSeverityError,
7699
})
77100

101+
report.AddCheck(ValidationCheck{
102+
Description: "Credential usage definitions are uniquely identifiable inside an executable",
103+
Assertion: AreCredentialUsagesUniquelyIdentifiable(e),
104+
Severity: ValidationSeverityError,
105+
})
106+
78107
return report.IsValid(), report
79108
}
80109

81110
func (e Executable) Command() string {
82111
return strings.Join(e.Runs, " ")
83112
}
113+
114+
func (c CredentialUsage) Validate() (bool, ValidationReport) {
115+
report := ValidationReport{
116+
Heading: fmt.Sprintf("Credential usage %s", c.ID()),
117+
Checks: []ValidationCheck{},
118+
}
119+
report.AddCheck(ValidationCheck{
120+
Description: "If defined, a credential reference must have at least a `Name`",
121+
Assertion: c.Name != "" || (c.Plugin == "" && c.Provisioner == nil),
122+
Severity: ValidationSeverityError,
123+
})
124+
125+
report.AddCheck(ValidationCheck{
126+
Description: "If defined, a credential selection must have its `ID` and `IncludeAllCredentials` set",
127+
Assertion: c.SelectFrom == nil || (c.SelectFrom.ID != "" && c.SelectFrom.IncludeAllCredentials),
128+
Severity: ValidationSeverityError,
129+
})
130+
131+
report.AddCheck(ValidationCheck{
132+
Description: "Credential usage has either a credential reference or selection defined, but not both",
133+
Assertion: (c.SelectFrom != nil || c.Name != "") && !(c.SelectFrom != nil && c.Name != ""),
134+
Severity: ValidationSeverityError,
135+
})
136+
return report.IsValid(), report
137+
}
138+
139+
// ID returns the identifier of this credential usage at the scope of its executable
140+
func (c CredentialUsage) ID() string {
141+
if c.Name != "" {
142+
if c.Plugin != "" {
143+
return strings.Join([]string{c.Name.ID().String(), c.Plugin}, "|")
144+
} else {
145+
return c.Name.ID().String()
146+
}
147+
}
148+
149+
if c.SelectFrom != nil && c.SelectFrom.ID != "" {
150+
return c.SelectFrom.ID
151+
}
152+
153+
return ""
154+
}

sdk/schema/plugin.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ func (p Plugin) Validate() (bool, ValidationReport) {
6868

6969
report.AddCheck(ValidationCheck{
7070
Description: "Has a credential type or executable defined",
71-
Assertion: len(p.Credentials) > 0 && len(p.Executables) > 0,
71+
Assertion: len(p.Credentials) > 0 || len(p.Executables) > 0,
7272
Severity: ValidationSeverityError,
7373
})
7474

7575
report.AddCheck(ValidationCheck{
7676
Description: "Has no more than one credential type defined. Plugins with multiple credential types are not supported yet",
77-
Assertion: len(p.Credentials) == 1,
77+
Assertion: len(p.Credentials) <= 1,
7878
Severity: ValidationSeverityError,
7979
})
8080

@@ -107,6 +107,12 @@ func (p Plugin) DeepValidate() []ValidationReport {
107107
for _, exe := range p.Executables {
108108
_, exeReport := exe.Validate()
109109
reports = append(reports, exeReport)
110+
111+
for _, usage := range exe.Uses {
112+
_, usageReport := usage.Validate()
113+
usageReport.Heading = fmt.Sprintf("Executable %s: %s", exe.Name, usageReport.Heading)
114+
reports = append(reports, usageReport)
115+
}
110116
}
111117

112118
return reports

sdk/schema/validation.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,17 @@ func ContainsLowercaseLettersOrDigits(str string) bool {
9898
func CredentialReferencesInCredentialList(plugin Plugin) bool {
9999
for _, executable := range plugin.Executables {
100100
for _, execCredential := range executable.Uses {
101-
found := false
102-
for _, credential := range plugin.Credentials {
103-
if execCredential.Name == credential.Name {
104-
found = true
105-
break
101+
if execCredential.Name != "" {
102+
found := false
103+
for _, credential := range plugin.Credentials {
104+
if execCredential.Name == credential.Name {
105+
found = true
106+
break
107+
}
108+
}
109+
if !found {
110+
return false
106111
}
107-
}
108-
if !found {
109-
return false
110112
}
111113
}
112114
}
@@ -122,6 +124,15 @@ func NoDuplicateCredentials(plugin Plugin) bool {
122124
return IsStringSliceASet(ids)
123125
}
124126

127+
func AreCredentialUsagesUniquelyIdentifiable(executable Executable) bool {
128+
var usageIds []string
129+
for _, credentialUsage := range executable.Uses {
130+
usageIds = append(usageIds, credentialUsage.ID())
131+
}
132+
133+
return IsStringSliceASet(usageIds)
134+
}
135+
125136
func IsStringSliceASet(slice []string) bool {
126137
for i, s := range slice {
127138
if i == len(slice)-1 {

0 commit comments

Comments
 (0)