-
Notifications
You must be signed in to change notification settings - Fork 25
feat: Add non-blocking warning for Terraform provider repo naming convention #1506
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package cmd | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/speakeasy-api/speakeasy/prompts" | ||
) | ||
|
||
func TestTerraformNamingWarningIntegration(t *testing.T) { | ||
// Test that the warning is properly detected and handled | ||
warning := &prompts.TerraformNamingWarning{RepoName: "test-repo"} | ||
|
||
if !prompts.IsTerraformNamingWarning(warning) { | ||
t.Error("Expected IsTerraformNamingWarning to return true for TerraformNamingWarning") | ||
} | ||
|
||
// Test that the warning message is properly formatted | ||
expectedMsg := "Terraform repository naming warning: repository 'test-repo' does not follow the required naming convention" | ||
if warning.Error() != expectedMsg { | ||
t.Errorf("Expected error message '%s', got '%s'", expectedMsg, warning.Error()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import ( | |
"os" | ||
"path" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/go-git/go-git/v5" | ||
|
@@ -38,6 +39,35 @@ const ( | |
terraformGPGPassPhraseDefault = "TERRAFORM_GPG_PASSPHRASE" | ||
) | ||
|
||
// TerraformNamingWarning is a custom error type for Terraform repository naming warnings | ||
type TerraformNamingWarning struct { | ||
RepoName string | ||
} | ||
|
||
func (w *TerraformNamingWarning) Error() string { | ||
return fmt.Sprintf("Terraform repository naming warning: repository '%s' does not follow the required naming convention", w.RepoName) | ||
} | ||
|
||
// IsTerraformNamingWarning checks if an error is a TerraformNamingWarning | ||
func IsTerraformNamingWarning(err error) bool { | ||
_, ok := err.(*TerraformNamingWarning) | ||
return ok | ||
} | ||
|
||
// checkTerraformRepositoryNaming checks if the repository name follows the Terraform naming convention | ||
// The public HashiCorp Terraform Registry requires terraform-provider-{NAME} where name is lowercase | ||
func checkTerraformRepositoryNaming(repoName string) error { | ||
// Check if the repository name follows the terraform-provider-{NAME} pattern | ||
// where NAME should be lowercase | ||
terraformProviderPattern := regexp.MustCompile(`^terraform-provider-([a-z0-9-]+)$`) | ||
|
||
if !terraformProviderPattern.MatchString(repoName) { | ||
return &TerraformNamingWarning{RepoName: repoName} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
var SupportedPublishingTargets = []string{ | ||
"csharp", | ||
"go", | ||
|
@@ -200,6 +230,21 @@ func ConfigurePublishing(target *workflow.Target, name string) (*workflow.Target | |
}, | ||
} | ||
case "terraform": | ||
// Check repository naming convention for Terraform | ||
if repo := FindGithubRepository("."); repo != nil { | ||
if remoteURL := ParseGithubRemoteURL(repo); remoteURL != "" { | ||
// Extract repository name from URL | ||
urlParts := strings.Split(remoteURL, "/") | ||
if len(urlParts) > 0 { | ||
repoName := urlParts[len(urlParts)-1] | ||
if err := checkTerraformRepositoryNaming(repoName); err != nil { | ||
// Return the target with the warning attached | ||
return target, err | ||
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. Since this
I think we want the latter by moving this logic below |
||
} | ||
} | ||
} | ||
} | ||
|
||
target.Publishing = &workflow.Publishing{ | ||
Terraform: &workflow.Terraform{ | ||
GPGPrivateKey: formatWorkflowSecret(terraformGPGPrivateKeyDefault), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package prompts | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestCheckTerraformRepositoryNaming(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
repoName string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "Valid terraform provider name", | ||
repoName: "terraform-provider-aws", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Valid terraform provider name with numbers", | ||
repoName: "terraform-provider-aws-v2", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Valid terraform provider name with hyphens", | ||
repoName: "terraform-provider-google-cloud", | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Invalid - missing terraform-provider prefix", | ||
repoName: "aws-provider", | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Invalid - uppercase letters", | ||
repoName: "terraform-provider-AWS", | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Invalid - starts with uppercase", | ||
repoName: "Terraform-provider-aws", | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Invalid - empty name after prefix", | ||
repoName: "terraform-provider-", | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Invalid - just terraform-provider", | ||
repoName: "terraform-provider", | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Invalid - special characters", | ||
repoName: "terraform-provider-aws@v2", | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := checkTerraformRepositoryNaming(tt.repoName) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("checkTerraformRepositoryNaming() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
|
||
if tt.wantErr { | ||
if !IsTerraformNamingWarning(err) { | ||
t.Errorf("Expected TerraformNamingWarning, got %T", err) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestIsTerraformNamingWarning(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
err error | ||
want bool | ||
}{ | ||
{ | ||
name: "TerraformNamingWarning", | ||
err: &TerraformNamingWarning{RepoName: "test"}, | ||
want: true, | ||
}, | ||
{ | ||
name: "Other error", | ||
err: &TerraformNamingWarning{RepoName: "test"}, | ||
want: true, | ||
}, | ||
{ | ||
name: "Nil error", | ||
err: nil, | ||
want: false, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := IsTerraformNamingWarning(tt.err); got != tt.want { | ||
t.Errorf("IsTerraformNamingWarning() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
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.
Nit: https://pkg.go.dev/errors#As can be used to avoid defining this function, makes it safer in case the error is wrapped by other errors during refactoring, and makes accessing inner details easier.
e.g. instead of
That logic can use something like this to quickly check the error type:
If you actually wanted to reference the error type fields, like
RepoName
, a variable can be used:Not necessary in this context since the error message already has
RepoName
in there, but just wanted to mention. 👍