From 5e5b2a49084e11549b6716b216c84e7753e23e36 Mon Sep 17 00:00:00 2001 From: Ryan Albert <42415738+ryan-timothy-albert@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:16:41 -0700 Subject: [PATCH] feat: fire go publishing events (#141) * feat: fire go publishing events * feat: fire go publishing events * feat: fire go publishing events * feat: update * feat: fix circular dependency * feat: test * feat: test --- internal/actions/publishEvent.go | 314 +------------------------- internal/git/releases.go | 15 ++ internal/telemetry/publishing.go | 364 +++++++++++++++++++++++++++++++ main.go | 2 +- 4 files changed, 385 insertions(+), 310 deletions(-) create mode 100644 internal/telemetry/publishing.go diff --git a/internal/actions/publishEvent.go b/internal/actions/publishEvent.go index feace108..34d393b1 100644 --- a/internal/actions/publishEvent.go +++ b/internal/actions/publishEvent.go @@ -1,331 +1,27 @@ package actions import ( - "context" "fmt" "os" - "path/filepath" "strings" - config "github.com/speakeasy-api/sdk-gen-config" - "github.com/speakeasy-api/sdk-generation-action/internal/environment" "github.com/speakeasy-api/sdk-generation-action/internal/telemetry" - "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/shared" ) -func PublishEvent() error { +func PublishEventAction() error { g, err := initAction() if err != nil { return err } - workspace := environment.GetWorkspace() - path := filepath.Join(workspace, "repo") - path = filepath.Join(path, os.Getenv("INPUT_TARGET_DIRECTORY")) - - return telemetry.Track(context.Background(), shared.InteractionTypePublish, func(ctx context.Context, event *shared.CliEvent) error { - registryName := os.Getenv("INPUT_REGISTRY_NAME") - if registryName != "" { - event.PublishPackageRegistryName = ®istryName - } - - loadedCfg, err := config.Load(path) - if err != nil { - return err - } - - if loadedCfg.LockFile == nil { - return fmt.Errorf("empty lock file for python language target in directory %s", path) - } - - version := processLockFile(*loadedCfg.LockFile, event) - - if strings.Contains(strings.ToLower(os.Getenv("GH_ACTION_RESULT")), "success") { + version, err := telemetry.TriggerPublishingEvent(os.Getenv("INPUT_TARGET_DIRECTORY"), os.Getenv("GH_ACTION_RESULT"), os.Getenv("INPUT_REGISTRY_NAME")) + if version != "" { + if strings.Contains(os.Getenv("GH_ACTION_RESULT"), "success") { if err = g.SetReleaseToPublished(version); err != nil { fmt.Println("Failed to set release to published %w", err) } } - - var processingErr error - switch os.Getenv("INPUT_REGISTRY_NAME") { - case "pypi": - processingErr = processPyPI(loadedCfg, event, path, version) - case "npm": - processingErr = processNPM(loadedCfg, event, path, version) - case "packagist": - processingErr = processPackagist(loadedCfg, event, path) - case "nuget": - processingErr = processNuget(loadedCfg, event, path, version) - case "gems": - processingErr = processGems(loadedCfg, event, path, version) - case "sonatype": - processingErr = processSonatype(loadedCfg, event, path, version) - case "terraform": - processingErr = processTerraform(loadedCfg, event, path, version) - } - - if processingErr != nil { - return processingErr - } - - event.Success = strings.Contains(strings.ToLower(os.Getenv("GH_ACTION_RESULT")), "success") - - return nil - }) -} - -func processPyPI(cfg *config.Config, event *shared.CliEvent, path string, version string) error { - lang := "python" - if cfg.Config == nil { - return fmt.Errorf("empty config for %s language target in directory %s", lang, path) - } - - langCfg, ok := cfg.Config.Languages[lang] - if !ok { - return fmt.Errorf("no %s config in directory %s", lang, path) - } - - event.GenerateTarget = &lang - - var packageName string - if name, ok := langCfg.Cfg["packageName"]; ok { - if strName, ok := name.(string); ok { - packageName = strName - } - } - - if packageName != "" { - event.PublishPackageName = &packageName - } - - if packageName != "" && version != "" { - publishURL := fmt.Sprintf("https://pypi.org/project/%s/%s", packageName, version) - event.PublishPackageURL = &publishURL - } - - return nil -} - -func processNPM(cfg *config.Config, event *shared.CliEvent, path string, version string) error { - lang := "typescript" - if cfg.Config == nil { - return fmt.Errorf("empty config for %s language target in directory %s", lang, path) - } - - langCfg, ok := cfg.Config.Languages[lang] - if !ok { - return fmt.Errorf("no %s config in directory %s", lang, path) - } - - event.GenerateTarget = &lang - - var packageName string - if name, ok := langCfg.Cfg["packageName"]; ok { - if strName, ok := name.(string); ok { - packageName = strName - } - } - - if packageName != "" { - event.PublishPackageName = &packageName - } - - if packageName != "" && version != "" { - publishURL := fmt.Sprintf("https://www.npmjs.com/package/%s/v/%s", packageName, version) - event.PublishPackageURL = &publishURL - } - - return nil -} - -func processPackagist(cfg *config.Config, event *shared.CliEvent, path string) error { - lang := "php" - if cfg.Config == nil { - return fmt.Errorf("empty config for %s language target in directory %s", lang, path) - } - - langCfg, ok := cfg.Config.Languages[lang] - if !ok { - return fmt.Errorf("no %s config in directory %s", lang, path) - } - - event.GenerateTarget = &lang - - var packageName string - if name, ok := langCfg.Cfg["packageName"]; ok { - if strName, ok := name.(string); ok { - packageName = strName - } - } - - if packageName != "" { - event.PublishPackageName = &packageName - } - - if packageName != "" { - publishURL := fmt.Sprintf("https://packagist.org/packages/%s", packageName) - event.PublishPackageURL = &publishURL - } - - return nil -} - -func processNuget(cfg *config.Config, event *shared.CliEvent, path string, version string) error { - lang := "csharp" - if cfg.Config == nil { - return fmt.Errorf("empty config for %s language target in directory %s", lang, path) - } - - langCfg, ok := cfg.Config.Languages[lang] - if !ok { - return fmt.Errorf("no %s config in directory %s", lang, path) - } - - event.GenerateTarget = &lang - - var packageName string - if name, ok := langCfg.Cfg["packageName"]; ok { - if strName, ok := name.(string); ok { - packageName = strName - } - } - - if packageName != "" { - event.PublishPackageName = &packageName - } - - if packageName != "" && version != "" { - publishURL := fmt.Sprintf("https://www.nuget.org/packages/%s/%s", packageName, version) - event.PublishPackageURL = &publishURL - } - - return nil -} - -func processGems(cfg *config.Config, event *shared.CliEvent, path string, version string) error { - lang := "ruby" - if cfg.Config == nil { - return fmt.Errorf("empty config for %s language target in directory %s", lang, path) - } - - langCfg, ok := cfg.Config.Languages[lang] - if !ok { - return fmt.Errorf("no %s config in directory %s", lang, path) - } - - event.GenerateTarget = &lang - - var packageName string - if name, ok := langCfg.Cfg["packageName"]; ok { - if strName, ok := name.(string); ok { - packageName = strName - } - } - - if packageName != "" { - event.PublishPackageName = &packageName - } - - if packageName != "" && version != "" { - publishURL := fmt.Sprintf("https://rubygems.org/gems/%s/%s", packageName, version) - event.PublishPackageURL = &publishURL - } - - return nil -} - -func processSonatype(cfg *config.Config, event *shared.CliEvent, path string, version string) error { - lang := "java" - if cfg.Config == nil { - return fmt.Errorf("empty config for %s language target in directory %s", lang, path) - } - - langCfg, ok := cfg.Config.Languages[lang] - if !ok { - return fmt.Errorf("no %s config in directory %s", lang, path) - } - - event.GenerateTarget = &lang - - var groupID string - if name, ok := langCfg.Cfg["groupID"]; ok { - if strName, ok := name.(string); ok { - groupID = strName - } - } - - var artifactID string - if name, ok := langCfg.Cfg["artifactID"]; ok { - if strName, ok := name.(string); ok { - artifactID = strName - } - } - - // TODO: Figure out how to represent java legacy publish URL - if groupID != "" && artifactID != "" { - combinedPackage := fmt.Sprintf("%s/%s", groupID, artifactID) - event.PublishPackageName = &combinedPackage - } - - if groupID != "" && artifactID != "" && version != "" { - publishURL := fmt.Sprintf("https://central.sonatype.com/artifact/%s/%s/%s", groupID, artifactID, version) - event.PublishPackageURL = &publishURL - } - - return nil -} - -func processTerraform(cfg *config.Config, event *shared.CliEvent, path string, version string) error { - lang := "terraform" - if cfg.Config == nil { - return fmt.Errorf("empty config for %s language target in directory %s", lang, path) - } - - langCfg, ok := cfg.Config.Languages[lang] - if !ok { - return fmt.Errorf("no %s config in directory %s", lang, path) - } - - event.GenerateTarget = &lang - - var packageName string - if name, ok := langCfg.Cfg["packageName"]; ok { - if strName, ok := name.(string); ok { - packageName = strName - } - } - - var author string - if name, ok := langCfg.Cfg["author"]; ok { - if strName, ok := name.(string); ok { - author = strName - } - } - - if packageName != "" { - event.PublishPackageName = &packageName - } - - if packageName != "" && author != "" && version != "" { - publishURL := fmt.Sprintf("https://registry.terraform.io/providers/%s/%s/%s", author, packageName, version) - event.PublishPackageURL = &publishURL - } - - return nil -} - -func processLockFile(lockFile config.LockFile, event *shared.CliEvent) string { - if lockFile.ID != "" { - event.GenerateGenLockID = &lockFile.ID - } - - if lockFile.Management.ReleaseVersion != "" { - event.PublishPackageVersion = &lockFile.Management.ReleaseVersion - } - - if lockFile.Management.SpeakeasyVersion != "" { - event.SpeakeasyVersion = lockFile.Management.SpeakeasyVersion } - return lockFile.Management.ReleaseVersion + return err } diff --git a/internal/git/releases.go b/internal/git/releases.go index b2c4b0fc..8e7b301f 100644 --- a/internal/git/releases.go +++ b/internal/git/releases.go @@ -11,6 +11,7 @@ import ( "github.com/google/go-github/v54/github" "github.com/speakeasy-api/sdk-generation-action/internal/environment" + "github.com/speakeasy-api/sdk-generation-action/internal/telemetry" "github.com/speakeasy-api/sdk-generation-action/pkg/releases" ) @@ -94,6 +95,7 @@ func (g *Git) CreateRelease(releaseInfo releases.ReleasesInfo, outputs map[strin Name: github.String(fmt.Sprintf("%s - %s - %s", lang, tag, environment.GetInvokeTime().Format("2006-01-02 15:04:05"))), Body: github.String(fmt.Sprintf(`# Generated by Speakeasy CLI%s`, releaseInfo)), }) + if err != nil { if release, _, err := g.client.Repositories.GetReleaseByTag(context.Background(), os.Getenv("GITHUB_REPOSITORY_OWNER"), getRepo(), *tagName); err == nil && release != nil { if release.Body != nil && strings.Contains(*release.Body, "Publishing Completed") { @@ -106,8 +108,21 @@ func (g *Git) CreateRelease(releaseInfo releases.ReleasesInfo, outputs map[strin // TODO: Consider deleting and recreating the release if we are moving forward with publishing return nil } + // Go has no publishing job, so we publish a CLI event on github release here + if lang == "go" { + if _, publishEventErr := telemetry.TriggerPublishingEvent(info.Path, "failed", "go"); publishEventErr != nil { + fmt.Printf("failed to write publishing event: %v\n", publishEventErr) + } + } return fmt.Errorf("failed to create release for tag %s: %w", *tagName, err) + } else { + // Go has no publishing job, so we publish a CLI event on github release here + if lang == "go" { + if _, publishEventErr := telemetry.TriggerPublishingEvent(info.Path, "success", "go"); publishEventErr != nil { + fmt.Printf("failed to write publishing event: %v\n", publishEventErr) + } + } } } } diff --git a/internal/telemetry/publishing.go b/internal/telemetry/publishing.go new file mode 100644 index 00000000..d7068bc8 --- /dev/null +++ b/internal/telemetry/publishing.go @@ -0,0 +1,364 @@ +package telemetry + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + config "github.com/speakeasy-api/sdk-gen-config" + "github.com/speakeasy-api/sdk-generation-action/internal/environment" + "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/shared" +) + +func TriggerPublishingEvent(targetDirectory, result, registryName string) (string, error) { + workspace := environment.GetWorkspace() + path := filepath.Join(workspace, "repo") + path = filepath.Join(path, targetDirectory) + + var packageVersion string + return packageVersion, Track(context.Background(), shared.InteractionTypePublish, func(ctx context.Context, event *shared.CliEvent) error { + if registryName != "" { + event.PublishPackageRegistryName = ®istryName + } + + loadedCfg, err := config.Load(path) + if err != nil { + return err + } + + if loadedCfg.LockFile == nil { + return fmt.Errorf("empty lock file for python language target in directory %s", path) + } + + version := processLockFile(*loadedCfg.LockFile, event) + packageVersion = version + + var processingErr error + switch registryName { + case "pypi": + processingErr = processPyPI(loadedCfg, event, path, version) + case "npm": + processingErr = processNPM(loadedCfg, event, path, version) + case "packagist": + processingErr = processPackagist(loadedCfg, event, path) + case "nuget": + processingErr = processNuget(loadedCfg, event, path, version) + case "gems": + processingErr = processGems(loadedCfg, event, path, version) + case "sonatype": + processingErr = processSonatype(loadedCfg, event, path, version) + case "terraform": + processingErr = processTerraform(loadedCfg, event, path, version) + case "go": + processingErr = processGo(loadedCfg, event, path, version) + } + + if processingErr != nil { + return processingErr + } + + event.Success = strings.Contains(strings.ToLower(result), "success") + + return nil + }) +} + +func processPyPI(cfg *config.Config, event *shared.CliEvent, path string, version string) error { + lang := "python" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var packageName string + if name, ok := langCfg.Cfg["packageName"]; ok { + if strName, ok := name.(string); ok { + packageName = strName + } + } + + if packageName != "" { + event.PublishPackageName = &packageName + } + + if packageName != "" && version != "" { + publishURL := fmt.Sprintf("https://pypi.org/project/%s/%s", packageName, version) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processNPM(cfg *config.Config, event *shared.CliEvent, path string, version string) error { + lang := "typescript" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var packageName string + if name, ok := langCfg.Cfg["packageName"]; ok { + if strName, ok := name.(string); ok { + packageName = strName + } + } + + if packageName != "" { + event.PublishPackageName = &packageName + } + + if packageName != "" && version != "" { + publishURL := fmt.Sprintf("https://www.npmjs.com/package/%s/v/%s", packageName, version) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processGo(cfg *config.Config, event *shared.CliEvent, path string, version string) error { + lang := "go" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var packageName string + if name, ok := langCfg.Cfg["packageName"]; ok { + if strName, ok := name.(string); ok { + packageName = strName + } + } + + if packageName != "" { + event.PublishPackageName = &packageName + } + + if packageName != "" && version != "" { + relPath, err := filepath.Rel(filepath.Join(environment.GetWorkspace(), "repo"), path) + if err != nil { + return err + } + + tag := fmt.Sprintf("v%s", version) + if relPath != "" && relPath != "." && relPath != "./" { + tag = fmt.Sprintf("%s/%s", relPath, tag) + } + + publishURL := fmt.Sprintf("https://github.com/%s/releases/tag/%s", os.Getenv("GITHUB_REPOSITORY"), tag) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processPackagist(cfg *config.Config, event *shared.CliEvent, path string) error { + lang := "php" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var packageName string + if name, ok := langCfg.Cfg["packageName"]; ok { + if strName, ok := name.(string); ok { + packageName = strName + } + } + + if packageName != "" { + event.PublishPackageName = &packageName + } + + if packageName != "" { + publishURL := fmt.Sprintf("https://packagist.org/packages/%s", packageName) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processNuget(cfg *config.Config, event *shared.CliEvent, path string, version string) error { + lang := "csharp" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var packageName string + if name, ok := langCfg.Cfg["packageName"]; ok { + if strName, ok := name.(string); ok { + packageName = strName + } + } + + if packageName != "" { + event.PublishPackageName = &packageName + } + + if packageName != "" && version != "" { + publishURL := fmt.Sprintf("https://www.nuget.org/packages/%s/%s", packageName, version) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processGems(cfg *config.Config, event *shared.CliEvent, path string, version string) error { + lang := "ruby" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var packageName string + if name, ok := langCfg.Cfg["packageName"]; ok { + if strName, ok := name.(string); ok { + packageName = strName + } + } + + if packageName != "" { + event.PublishPackageName = &packageName + } + + if packageName != "" && version != "" { + publishURL := fmt.Sprintf("https://rubygems.org/gems/%s/%s", packageName, version) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processSonatype(cfg *config.Config, event *shared.CliEvent, path string, version string) error { + lang := "java" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var groupID string + if name, ok := langCfg.Cfg["groupID"]; ok { + if strName, ok := name.(string); ok { + groupID = strName + } + } + + var artifactID string + if name, ok := langCfg.Cfg["artifactID"]; ok { + if strName, ok := name.(string); ok { + artifactID = strName + } + } + + // TODO: Figure out how to represent java legacy publish URL + if groupID != "" && artifactID != "" { + combinedPackage := fmt.Sprintf("%s/%s", groupID, artifactID) + event.PublishPackageName = &combinedPackage + } + + if groupID != "" && artifactID != "" && version != "" { + publishURL := fmt.Sprintf("https://central.sonatype.com/artifact/%s/%s/%s", groupID, artifactID, version) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processTerraform(cfg *config.Config, event *shared.CliEvent, path string, version string) error { + lang := "terraform" + if cfg.Config == nil { + return fmt.Errorf("empty config for %s language target in directory %s", lang, path) + } + + langCfg, ok := cfg.Config.Languages[lang] + if !ok { + return fmt.Errorf("no %s config in directory %s", lang, path) + } + + event.GenerateTarget = &lang + + var packageName string + if name, ok := langCfg.Cfg["packageName"]; ok { + if strName, ok := name.(string); ok { + packageName = strName + } + } + + var author string + if name, ok := langCfg.Cfg["author"]; ok { + if strName, ok := name.(string); ok { + author = strName + } + } + + if packageName != "" { + event.PublishPackageName = &packageName + } + + if packageName != "" && author != "" && version != "" { + publishURL := fmt.Sprintf("https://registry.terraform.io/providers/%s/%s/%s", author, packageName, version) + event.PublishPackageURL = &publishURL + } + + return nil +} + +func processLockFile(lockFile config.LockFile, event *shared.CliEvent) string { + if lockFile.ID != "" { + event.GenerateGenLockID = &lockFile.ID + } + + if lockFile.Management.ReleaseVersion != "" { + event.PublishPackageVersion = &lockFile.Management.ReleaseVersion + } + + if lockFile.Management.SpeakeasyVersion != "" { + event.SpeakeasyVersion = lockFile.Management.SpeakeasyVersion + } + + return lockFile.Management.ReleaseVersion +} diff --git a/main.go b/main.go index 8d92d7fa..a0516643 100644 --- a/main.go +++ b/main.go @@ -50,7 +50,7 @@ func main() { case environment.ActionLog: return actions.LogActionResult() case environment.ActionPublishEvent: - return actions.PublishEvent() + return actions.PublishEventAction() case environment.ActionTag: return actions.Tag() default: