diff --git a/cmd/status.go b/cmd/status.go index abcc6d1de..1204d65b7 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -5,6 +5,7 @@ package cmd import ( + "encoding/json" "errors" "fmt" "io" @@ -37,6 +38,9 @@ const ( categoriesParameter = "categories" elasticsearchSubscriptionParameter = "elastic.subscription" serverlessProjectTypesParameter = "serverless.project_types" + + statusTableFormat = "table" + statusJSONFormat = "json" ) var ( @@ -50,6 +54,11 @@ var ( elasticsearchSubscriptionParameter, serverlessProjectTypesParameter, } + + availableFormatsParameters = []string{ + statusTableFormat, + statusJSONFormat, + } ) func setupStatusCommand() *cobraext.Command { @@ -63,6 +72,7 @@ func setupStatusCommand() *cobraext.Command { cmd.Flags().BoolP(cobraext.ShowAllFlagName, "a", false, cobraext.ShowAllFlagDescription) cmd.Flags().String(cobraext.StatusKibanaVersionFlagName, "", cobraext.StatusKibanaVersionFlagDescription) cmd.Flags().StringSlice(cobraext.StatusExtraInfoFlagName, nil, fmt.Sprintf(cobraext.StatusExtraInfoFlagDescription, strings.Join(availableExtraInfoParameters, ","))) + cmd.Flags().String(cobraext.StatusFormatFlagName, "table", fmt.Sprintf(cobraext.StatusFormatFlagDescription, strings.Join(availableFormatsParameters, ","))) return cobraext.NewCommand(cmd, cobraext.ContextPackage) } @@ -86,6 +96,13 @@ func statusCommandAction(cmd *cobra.Command, args []string) error { if err != nil { return cobraext.FlagParsingError(err, cobraext.StatusExtraInfoFlagName) } + format, err := cmd.Flags().GetString(cobraext.StatusFormatFlagName) + if err != nil { + return cobraext.FlagParsingError(err, cobraext.StatusFormatFlagName) + } + if !slices.Contains(availableFormatsParameters, format) { + return cobraext.FlagParsingError(fmt.Errorf("unsupported format %q, supported formats: %s", format, strings.Join(availableFormatsParameters, ",")), cobraext.StatusFormatFlagName) + } err = validateExtraInfoParameters(extraParameters) if err != nil { @@ -115,7 +132,15 @@ func statusCommandAction(cmd *cobra.Command, args []string) error { } } - return print(packageStatus, os.Stdout, extraParameters) + switch format { + case "table": + return print(packageStatus, os.Stdout, extraParameters) + case "json": + return printJSON(packageStatus, os.Stdout, extraParameters) + default: + return errors.New("unknown format") + } + } func validateExtraInfoParameters(extraParameters []string) error { @@ -366,3 +391,86 @@ func releaseFromVersion(version string) string { return defaultText } + +type statusJSON struct { + Package string `json:"package"` + Owner string `json:"owner,omitempty"` + Versions []statusJSONVersion `json:"versions,omitempty"` + PendingChanges *changelog.Revision `json:"pending_changes,omitempty"` +} + +type statusJSONVersion struct { + Environment string `json:"environment,omitempty"` + Version string `json:"version"` + Release string `json:"release,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + + // Extra parameters + KibanaVersion string `json:"kibana_version,omitempty"` + Subscription string `json:"subscription,omitempty"` + Categories []string `json:"categories,omitempty"` + ServerlessProjectType string `json:"serverless_project_type,omitempty"` +} + +func newStatusJSONVersion(environment string, manifest packages.PackageManifest, extraParameters []string) statusJSONVersion { + version := statusJSONVersion{ + Environment: environment, + Version: manifest.Version, + Release: strings.ToLower(releaseFromVersion(manifest.Version)), + Title: manifest.Title, + Description: manifest.Description, + } + + for _, param := range extraParameters { + switch param { + case kibanaVersionParameter: + version.KibanaVersion = manifest.Conditions.Kibana.Version + case categoriesParameter: + version.Categories = manifest.Categories + case elasticsearchSubscriptionParameter: + version.Subscription = manifest.Conditions.Elastic.Subscription + } + } + + return version +} + +func printJSON(p *status.PackageStatus, w io.Writer, extraParameters []string) error { + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.SetIndent("", " ") + + owner := formatOwner(p) + if owner == "-" { + owner = "" + } + + info := statusJSON{ + Package: p.Name, + Owner: owner, + } + + if manifest := p.Local; manifest != nil { + version := newStatusJSONVersion("local", *manifest, extraParameters) + info.Versions = append(info.Versions, version) + info.PendingChanges = p.PendingChanges + } + + for _, manifest := range p.Production { + version := newStatusJSONVersion("production", manifest, extraParameters) + info.Versions = append(info.Versions, version) + } + + if slices.Contains(extraParameters, serverlessProjectTypesParameter) { + for _, projectType := range p.Serverless { + for _, manifest := range projectType.Manifests { + version := newStatusJSONVersion("production", manifest, extraParameters) + version.ServerlessProjectType = projectType.Name + info.Versions = append(info.Versions, version) + } + } + } + + return enc.Encode(info) +} diff --git a/cmd/status_test.go b/cmd/status_test.go index 3ad167a6c..bab846279 100644 --- a/cmd/status_test.go +++ b/cmd/status_test.go @@ -187,6 +187,14 @@ func TestStatusFormatAndPrint(t *testing.T) { assertOutputWithFile(t, c.expected, buf.String()) }) + + t.Run(c.title+"/JSON", func(t *testing.T) { + var buf bytes.Buffer + err := printJSON(c.pkgStatus, &buf, c.extraParameters) + require.NoError(t, err) + + assertOutputWithFile(t, c.expected+".json", buf.String()) + }) } } diff --git a/cmd/testdata/status-beta-versions.json b/cmd/testdata/status-beta-versions.json new file mode 100644 index 000000000..68420296a --- /dev/null +++ b/cmd/testdata/status-beta-versions.json @@ -0,0 +1,19 @@ +{ + "package": "foo", + "versions": [ + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.1.0-beta1", + "release": "beta", + "title": "Foo", + "description": "Foo integration" + } + ] +} diff --git a/cmd/testdata/status-extra-parameters.json b/cmd/testdata/status-extra-parameters.json new file mode 100644 index 000000000..c9e74aba4 --- /dev/null +++ b/cmd/testdata/status-extra-parameters.json @@ -0,0 +1,28 @@ +{ + "package": "foo", + "owner": "team", + "versions": [ + { + "environment": "local", + "version": "2.0.0-rc1", + "release": "release candidate", + "title": "Foo", + "description": "Foo integration", + "kibana_version": "^8.9.0", + "categories": [ + "custom" + ] + }, + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration", + "kibana_version": "^8.8.0", + "categories": [ + "custom" + ] + } + ] +} diff --git a/cmd/testdata/status-local-version-stage.json b/cmd/testdata/status-local-version-stage.json new file mode 100644 index 000000000..6224dde07 --- /dev/null +++ b/cmd/testdata/status-local-version-stage.json @@ -0,0 +1,41 @@ +{ + "package": "foo", + "owner": "team", + "versions": [ + { + "environment": "local", + "version": "2.0.0-rc1", + "release": "release candidate", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.1", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.2", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.1.0-beta1", + "release": "beta", + "title": "Foo", + "description": "Foo integration" + } + ] +} diff --git a/cmd/testdata/status-no-versions.json b/cmd/testdata/status-no-versions.json new file mode 100644 index 000000000..4de2bc8da --- /dev/null +++ b/cmd/testdata/status-no-versions.json @@ -0,0 +1,3 @@ +{ + "package": "foo" +} diff --git a/cmd/testdata/status-pending-changes.json b/cmd/testdata/status-pending-changes.json new file mode 100644 index 000000000..8296045e1 --- /dev/null +++ b/cmd/testdata/status-pending-changes.json @@ -0,0 +1,30 @@ +{ + "package": "foo", + "owner": "team", + "versions": [ + { + "environment": "local", + "version": "2.0.0-rc1", + "release": "release candidate", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + } + ], + "pending_changes": { + "version": "2.0.0-rc2", + "changes": [ + { + "description": "New feature", + "type": "enhancement", + "link": "http:github.com/org/repo/pull/2" + } + ] + } +} diff --git a/cmd/testdata/status-preview-versions.json b/cmd/testdata/status-preview-versions.json new file mode 100644 index 000000000..af84007f9 --- /dev/null +++ b/cmd/testdata/status-preview-versions.json @@ -0,0 +1,26 @@ +{ + "package": "foo", + "versions": [ + { + "environment": "production", + "version": "0.9.0", + "release": "technical preview", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.0-preview1", + "release": "technical preview", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.0-preview5", + "release": "technical preview", + "title": "Foo", + "description": "Foo integration" + } + ] +} diff --git a/cmd/testdata/status-release-candidate-versions.json b/cmd/testdata/status-release-candidate-versions.json new file mode 100644 index 000000000..59304245d --- /dev/null +++ b/cmd/testdata/status-release-candidate-versions.json @@ -0,0 +1,26 @@ +{ + "package": "foo", + "versions": [ + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.1.0-beta1", + "release": "beta", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "2.0.0-rc1", + "release": "release candidate", + "title": "Foo", + "description": "Foo integration" + } + ] +} diff --git a/cmd/testdata/status-serverless-projects.json b/cmd/testdata/status-serverless-projects.json new file mode 100644 index 000000000..42e2b561d --- /dev/null +++ b/cmd/testdata/status-serverless-projects.json @@ -0,0 +1,36 @@ +{ + "package": "foo", + "owner": "team", + "versions": [ + { + "environment": "local", + "version": "2.0.0-rc1", + "release": "release candidate", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + }, + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration", + "serverless_project_type": "observability" + }, + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration", + "serverless_project_type": "security" + } + ] +} diff --git a/cmd/testdata/status-version-one-stage.json b/cmd/testdata/status-version-one-stage.json new file mode 100644 index 000000000..2b93b8e47 --- /dev/null +++ b/cmd/testdata/status-version-one-stage.json @@ -0,0 +1,12 @@ +{ + "package": "foo", + "versions": [ + { + "environment": "production", + "version": "1.0.0", + "release": "ga", + "title": "Foo", + "description": "Foo integration" + } + ] +} diff --git a/internal/cobraext/flags.go b/internal/cobraext/flags.go index 2faaa4863..748061790 100644 --- a/internal/cobraext/flags.go +++ b/internal/cobraext/flags.go @@ -189,6 +189,9 @@ const ( StatusExtraInfoFlagName = "info" StatusExtraInfoFlagDescription = "show additional information (comma-separated values: \"%s\")" + StatusFormatFlagName = "format" + StatusFormatFlagDescription = "output format (\"%s\")" + TestCoverageFlagName = "test-coverage" TestCoverageFlagDescription = "enable test coverage reports"