Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,21 @@ func configurePublishing(ctx context.Context, flags ConfigureGithubFlags) error
target := workflowFile.Targets[name]
modifiedTarget, err := prompts.ConfigurePublishing(&target, name)
if err != nil {
// Check if this is a Terraform naming warning
if prompts.IsTerraformNamingWarning(err) {
// Display the warning and continue with the configuration
logger.Println(styles.RenderWarningMessage("HashiCorp Terraform Registry repository naming requirement",
"The public HashiCorp Terraform Registry requires Terraform Provider",
"repositories to have a naming convention of terraform-provider-NAME,",
"where name is lowercase characters.",
"",
"Reference: https://developer.hashicorp.com/terraform/registry/providers/publishing#preparing-your-provider"))
logger.Println("")

// Continue with the configuration despite the warning
workflowFile.Targets[name] = *modifiedTarget
continue
}
return err
}
workflowFile.Targets[name] = *modifiedTarget
Expand Down
22 changes: 22 additions & 0 deletions cmd/configure_test.go
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())
}
}
45 changes: 45 additions & 0 deletions prompts/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"

"github.com/go-git/go-git/v5"
Expand Down Expand Up @@ -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
}
Comment on lines +51 to +55
Copy link
Member

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

if prompts.IsTerraformNamingWarning(err) {

That logic can use something like this to quickly check the error type:

if errors.As(err, new(prompts.TerraformNamingWarning)) {

If you actually wanted to reference the error type fields, likeRepoName, a variable can be used:

var terraformNamingWarningErr prompts.TerraformNamingWarning
if errorrs.As(err, &terraformNamingWarningErr) {
  // now we have terraformNamingWarningErr.RepoName

Not necessary in this context since the error message already has RepoName in there, but just wanted to mention. 👍


// 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",
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this return is happening before target.Publishing is being set, technically its acting more like an error rather than a warning. I think we'll want to decide what's better or worse here:

  • (Current) Not updating the workflow configuration.
  • (Alternative) Updating the workflow configuration, then raising the warning.

I think we want the latter by moving this logic below target.Publishing =, since its possible for someone to rectify the issue outside the Speakeasy CLI (unless there's other steps necessary).

}
}
}
}

target.Publishing = &workflow.Publishing{
Terraform: &workflow.Terraform{
GPGPrivateKey: formatWorkflowSecret(terraformGPGPrivateKeyDefault),
Expand Down
107 changes: 107 additions & 0 deletions prompts/github_test.go
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)
}
})
}
}