From c4f48e8fbea5cc14282d51ca48700a431f36048c Mon Sep 17 00:00:00 2001 From: Ash Godfrey Date: Wed, 9 Jul 2025 16:29:11 +0100 Subject: [PATCH 1/2] Add non-blocking warning for Terraform provider repo naming convention - Add TerraformNamingWarning type and validation logic - Display styled warning when repository doesn't follow terraform-provider-{NAME} pattern - Warning is non-blocking and includes reference to HashiCorp docs - Add comprehensive unit and integration tests --- cmd/configure.go | 15 ++++++ cmd/configure_test.go | 22 +++++++++ prompts/github.go | 45 +++++++++++++++++ prompts/github_test.go | 107 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 cmd/configure_test.go create mode 100644 prompts/github_test.go diff --git a/cmd/configure.go b/cmd/configure.go index 24254b3ff..570963452 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -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 diff --git a/cmd/configure_test.go b/cmd/configure_test.go new file mode 100644 index 000000000..cc7950b6f --- /dev/null +++ b/cmd/configure_test.go @@ -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()) + } +} \ No newline at end of file diff --git a/prompts/github.go b/prompts/github.go index 67b4db020..d580b7126 100644 --- a/prompts/github.go +++ b/prompts/github.go @@ -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 + } + } + } + } + target.Publishing = &workflow.Publishing{ Terraform: &workflow.Terraform{ GPGPrivateKey: formatWorkflowSecret(terraformGPGPrivateKeyDefault), diff --git a/prompts/github_test.go b/prompts/github_test.go new file mode 100644 index 000000000..5da56bfa5 --- /dev/null +++ b/prompts/github_test.go @@ -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) + } + }) + } +} \ No newline at end of file From 49e965bb46a5540b36a05ccf5d332076701bd0d9 Mon Sep 17 00:00:00 2001 From: Ash Godfrey Date: Wed, 9 Jul 2025 16:47:27 +0100 Subject: [PATCH 2/2] chore: run linter --- cmd/configure.go | 2 +- cmd/configure_test.go | 6 +++--- prompts/github.go | 2 +- prompts/github_test.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/configure.go b/cmd/configure.go index 570963452..7dc5cb45d 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -479,7 +479,7 @@ func configurePublishing(ctx context.Context, flags ConfigureGithubFlags) error "", "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 diff --git a/cmd/configure_test.go b/cmd/configure_test.go index cc7950b6f..af10f4130 100644 --- a/cmd/configure_test.go +++ b/cmd/configure_test.go @@ -9,14 +9,14 @@ import ( 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()) } -} \ No newline at end of file +} diff --git a/prompts/github.go b/prompts/github.go index d580b7126..533d9617c 100644 --- a/prompts/github.go +++ b/prompts/github.go @@ -244,7 +244,7 @@ func ConfigurePublishing(target *workflow.Target, name string) (*workflow.Target } } } - + target.Publishing = &workflow.Publishing{ Terraform: &workflow.Terraform{ GPGPrivateKey: formatWorkflowSecret(terraformGPGPrivateKeyDefault), diff --git a/prompts/github_test.go b/prompts/github_test.go index 5da56bfa5..218913a19 100644 --- a/prompts/github_test.go +++ b/prompts/github_test.go @@ -104,4 +104,4 @@ func TestIsTerraformNamingWarning(t *testing.T) { } }) } -} \ No newline at end of file +}