diff --git a/cmd/configure.go b/cmd/configure.go index 24254b3f..7dc5cb45 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 00000000..af10f413 --- /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()) + } +} diff --git a/prompts/github.go b/prompts/github.go index 67b4db02..533d9617 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 00000000..218913a1 --- /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) + } + }) + } +}