diff --git a/README.md b/README.md index 16381df5..27e89dcf 100644 --- a/README.md +++ b/README.md @@ -457,6 +457,27 @@ The run "C1234" can be cancelled using the following command: galasactl runs cancel --name C1234 ``` +## monitors get + +This command can be used to get the details of monitors, like resource cleanup monitors, that are available in the Galasa service. + +By default, the output of `monitors get` will be in a "summary" format. To change the output format, you can supply the `--format` flag followed by the format that you wish the output to be displayed in. For a list of supported formats, view the command's help information by running `galasactl monitors get --help`. + +### Examples + +To get a list of all monitors that are currently available in the Galasa service, run the following command: + +``` +galasactl monitors get +``` + +If you would like to get a specific monitor, you can supply the name of the monitor with the `--name` flag. For example, the following command can be used to get a monitor named "myCustomMonitor": + +``` +galasactl monitors get --name myCustomMonitor +``` + +For a complete list of supported parameters see [here](./docs/generated/galasactl_monitors_get.md). ## properties get This command retrieves details of properties in a namespace. diff --git a/docs/generated/errors-list.md b/docs/generated/errors-list.md index 08062578..5993883b 100644 --- a/docs/generated/errors-list.md +++ b/docs/generated/errors-list.md @@ -214,7 +214,14 @@ The `galasactl` tool can generate the following errors: - GAL1215E: An attempt to update a user '{}' failed. Unexpected http status code {} received from the server. Error details from the server are not in a valid json format. Cause: '{}' - GAL1216E: An attempt to update a user '{}' failed. Unexpected http status code {} received from the server. Error details from the server are: '{}' - GAL1217E: An attempt to update a user '{}' failed. Unexpected http status code {} received from the server. Error details from the server are not in the json format. -- GAL1225E: Failed to open file '{}' cause: {}. Check that this file exists, and that you have read permissions. +- GAL1218E: Failed to get monitors. Sending the get request to the Galasa service failed. Cause is {} +- GAL1219E: Failed to get monitors. Unexpected http status code {} received from the server. +- GAL1220E: Failed to get monitors. Unexpected http status code {} received from the server. Error details from the server could not be read. Cause: {} +- GAL1221E: Failed to get monitors. Unexpected http status code {} received from the server. Error details from the server are not in a valid json format. Cause: '{}' +- GAL1222E: Failed to get monitors. Unexpected http status code {} received from the server. Error details from the server are: '{}' +- GAL1223E: Failed to get monitors. Unexpected http status code {} received from the server. Error details from the server are not in the json format. +- GAL1224E: Galasa Monitor named {} is not known on the Galasa service. +- GAL1225E: Invalid monitor name provided. The name provided with the --name flag cannot be empty and must only contain characters in the following ranges: 'a'-'z', 'A'-'Z', '0'-'9', '-' (dash), '_' (underscore). - GAL1226E: Internal failure. Contents of gzip could be read, but not decoded. New gzip reader failed: file: {} error: {} - GAL1227E: Internal failure. Contents of gzip could not be decoded. {} error: {} - GAL1228E: Internal failure. Contents of gzip could not be encoded and compressed. {} error: {} diff --git a/docs/generated/galasactl.md b/docs/generated/galasactl.md index c3b73e23..139f2054 100644 --- a/docs/generated/galasactl.md +++ b/docs/generated/galasactl.md @@ -18,6 +18,7 @@ A tool for controlling Galasa resources using the command-line. * [galasactl auth](galasactl_auth.md) - Manages authentication with a Galasa ecosystem * [galasactl local](galasactl_local.md) - Manipulate local system +* [galasactl monitors](galasactl_monitors.md) - Manage monitors in the Galasa service * [galasactl project](galasactl_project.md) - Manipulate local project source code * [galasactl properties](galasactl_properties.md) - Manages properties in an ecosystem * [galasactl resources](galasactl_resources.md) - Manages resources in an ecosystem diff --git a/docs/generated/galasactl_monitors.md b/docs/generated/galasactl_monitors.md new file mode 100644 index 00000000..3b5bc303 --- /dev/null +++ b/docs/generated/galasactl_monitors.md @@ -0,0 +1,29 @@ +## galasactl monitors + +Manage monitors in the Galasa service + +### Synopsis + +The parent command for operations to manipulate monitors in the Galasa service + +### Options + +``` + -b, --bootstrap string Bootstrap URL. Should start with 'http://' or 'file://'. If it starts with neither, it is assumed to be a fully-qualified path. If missing, it defaults to use the 'bootstrap.properties' file in your GALASA_HOME. Example: http://example.com/bootstrap, file:///user/myuserid/.galasa/bootstrap.properties , file://C:/Users/myuserid/.galasa/bootstrap.properties + -h, --help Displays the options for the 'monitors' command. + --rate-limit-retries int The maximum number of retries that should be made when requests to the Galasa Service fail due to rate limits being exceeded. Must be a whole number. Defaults to 3 retries (default 3) + --rate-limit-retry-backoff-secs float The amount of time in seconds to wait before retrying a command if it failed due to rate limits being exceeded. Defaults to 1 second. (default 1) +``` + +### Options inherited from parent commands + +``` + --galasahome string Path to a folder where Galasa will read and write files and configuration settings. The default is '${HOME}/.galasa'. This overrides the GALASA_HOME environment variable which may be set instead. + -l, --log string File to which log information will be sent. Any folder referred to must exist. An existing file will be overwritten. Specify "-" to log to stderr. Defaults to not logging. +``` + +### SEE ALSO + +* [galasactl](galasactl.md) - CLI for Galasa +* [galasactl monitors get](galasactl_monitors_get.md) - Get monitors from the Galasa service + diff --git a/docs/generated/galasactl_monitors_get.md b/docs/generated/galasactl_monitors_get.md new file mode 100644 index 00000000..f2430ca7 --- /dev/null +++ b/docs/generated/galasactl_monitors_get.md @@ -0,0 +1,34 @@ +## galasactl monitors get + +Get monitors from the Galasa service + +### Synopsis + +Get a list of monitors or a specific monitor from the Galasa service + +``` +galasactl monitors get [flags] +``` + +### Options + +``` + --format string the output format of the returned monitors. Supported formats are: 'summary', 'yaml'. (default "summary") + -h, --help Displays the options for the 'monitors get' command. + --name string An optional flag that identifies the monitor to be retrieved by name. +``` + +### Options inherited from parent commands + +``` + -b, --bootstrap string Bootstrap URL. Should start with 'http://' or 'file://'. If it starts with neither, it is assumed to be a fully-qualified path. If missing, it defaults to use the 'bootstrap.properties' file in your GALASA_HOME. Example: http://example.com/bootstrap, file:///user/myuserid/.galasa/bootstrap.properties , file://C:/Users/myuserid/.galasa/bootstrap.properties + --galasahome string Path to a folder where Galasa will read and write files and configuration settings. The default is '${HOME}/.galasa'. This overrides the GALASA_HOME environment variable which may be set instead. + -l, --log string File to which log information will be sent. Any folder referred to must exist. An existing file will be overwritten. Specify "-" to log to stderr. Defaults to not logging. + --rate-limit-retries int The maximum number of retries that should be made when requests to the Galasa Service fail due to rate limits being exceeded. Must be a whole number. Defaults to 3 retries (default 3) + --rate-limit-retry-backoff-secs float The amount of time in seconds to wait before retrying a command if it failed due to rate limits being exceeded. Defaults to 1 second. (default 1) +``` + +### SEE ALSO + +* [galasactl monitors](galasactl_monitors.md) - Manage monitors in the Galasa service + diff --git a/pkg/cmd/commandCollection.go b/pkg/cmd/commandCollection.go index 84beabf4..dad02192 100644 --- a/pkg/cmd/commandCollection.go +++ b/pkg/cmd/commandCollection.go @@ -39,6 +39,8 @@ const ( COMMAND_NAME_PROJECT_CREATE = "project create" COMMAND_NAME_LOCAL = "local" COMMAND_NAME_LOCAL_INIT = "local init" + COMMAND_NAME_MONITORS = "monitors" + COMMAND_NAME_MONITORS_GET = "monitors get" COMMAND_NAME_PROPERTIES = "properties" COMMAND_NAME_PROPERTIES_GET = "properties get" COMMAND_NAME_PROPERTIES_SET = "properties set" @@ -137,6 +139,10 @@ func (commands *commandCollectionImpl) init(factory spi.Factory) error { err = commands.addLocalCommands(factory, rootCommand) } + if err == nil { + err = commands.addMonitorsCommands(factory, rootCommand, commsFlagSet) + } + if err == nil { err = commands.addProjectCommands(factory, rootCommand) } @@ -430,6 +436,26 @@ func (commands *commandCollectionImpl) addSecretsCommands(factory spi.Factory, r return err } +func (commands *commandCollectionImpl) addMonitorsCommands(factory spi.Factory, rootCommand spi.GalasaCommand, commsFlagSet GalasaFlagSet) error { + + var err error + var monitorsCommand spi.GalasaCommand + var monitorsGetCommand spi.GalasaCommand + + monitorsCommand, err = NewMonitorsCmd(rootCommand, commsFlagSet) + + if err == nil { + monitorsGetCommand, err = NewMonitorsGetCommand(factory, monitorsCommand, commsFlagSet) + } + + if err == nil { + commands.commandMap[monitorsCommand.Name()] = monitorsCommand + commands.commandMap[monitorsGetCommand.Name()] = monitorsGetCommand + } + + return err +} + func (commands *commandCollectionImpl) addUsersCommands(factory spi.Factory, rootCommand spi.GalasaCommand, commsFlagSet GalasaFlagSet) error { var err error diff --git a/pkg/cmd/monitors.go b/pkg/cmd/monitors.go new file mode 100644 index 00000000..1dd50d20 --- /dev/null +++ b/pkg/cmd/monitors.go @@ -0,0 +1,93 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package cmd + +import ( + "github.com/galasa-dev/cli/pkg/spi" + "github.com/spf13/cobra" +) + +type MonitorsCmdValues struct { + name string +} + +type MonitorsCommand struct { + cobraCommand *cobra.Command + values *MonitorsCmdValues +} + +// ------------------------------------------------------------------------------------------------ +// Constructors +// ------------------------------------------------------------------------------------------------ + +func NewMonitorsCmd(rootCommand spi.GalasaCommand, commsFlagSet GalasaFlagSet) (spi.GalasaCommand, error) { + cmd := new(MonitorsCommand) + err := cmd.init(rootCommand, commsFlagSet) + return cmd, err +} + +// ------------------------------------------------------------------------------------------------ +// Public functions +// ------------------------------------------------------------------------------------------------ + +func (cmd *MonitorsCommand) Name() string { + return COMMAND_NAME_MONITORS +} + +func (cmd *MonitorsCommand) CobraCommand() *cobra.Command { + return cmd.cobraCommand +} + +func (cmd *MonitorsCommand) Values() interface{} { + return cmd.values +} + +// ------------------------------------------------------------------------------------------------ +// Private functions +// ------------------------------------------------------------------------------------------------ + +func (cmd *MonitorsCommand) init(rootCommand spi.GalasaCommand, commsFlagSet GalasaFlagSet) error { + + var err error + + cmd.values = &MonitorsCmdValues{} + cmd.cobraCommand, err = cmd.createCobraCommand(rootCommand, commsFlagSet) + + return err +} + +func (cmd *MonitorsCommand) createCobraCommand(rootCommand spi.GalasaCommand, commsFlagSet GalasaFlagSet) (*cobra.Command, error) { + + var err error + + monitorsCobraCmd := &cobra.Command{ + Use: "monitors", + Short: "Manage monitors in the Galasa service", + Long: "The parent command for operations to manipulate monitors in the Galasa service", + } + + monitorsCobraCmd.PersistentFlags().AddFlagSet(commsFlagSet.Flags()) + rootCommand.CobraCommand().AddCommand(monitorsCobraCmd) + + return monitorsCobraCmd, err +} + +func addMonitorNameFlag(cmd *cobra.Command, isMandatory bool, monitorsCmdValues *MonitorsCmdValues) { + + flagName := "name" + var description string + if isMandatory { + description = "A mandatory flag that identifies the monitor to be manipulated by name." + } else { + description = "An optional flag that identifies the monitor to be retrieved by name." + } + + cmd.Flags().StringVar(&monitorsCmdValues.name, flagName, "", description) + + if isMandatory { + cmd.MarkFlagRequired(flagName) + } +} diff --git a/pkg/cmd/monitorsGet.go b/pkg/cmd/monitorsGet.go new file mode 100644 index 00000000..4f4ff201 --- /dev/null +++ b/pkg/cmd/monitorsGet.go @@ -0,0 +1,144 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package cmd + +import ( + "log" + + "github.com/galasa-dev/cli/pkg/api" + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/galasa-dev/cli/pkg/monitors" + "github.com/galasa-dev/cli/pkg/spi" + "github.com/galasa-dev/cli/pkg/utils" + "github.com/spf13/cobra" +) + +type MonitorsGetCmdValues struct { + outputFormat string +} + +type MonitorsGetCommand struct { + values *MonitorsGetCmdValues + cobraCommand *cobra.Command +} + +// ------------------------------------------------------------------------------------------------ +// Constructors methods +// ------------------------------------------------------------------------------------------------ +func NewMonitorsGetCommand( + factory spi.Factory, + monitorsGetCommand spi.GalasaCommand, + commsFlagSet GalasaFlagSet, +) (spi.GalasaCommand, error) { + + cmd := new(MonitorsGetCommand) + + err := cmd.init(factory, monitorsGetCommand, commsFlagSet) + return cmd, err +} + +// ------------------------------------------------------------------------------------------------ +// Public methods +// ------------------------------------------------------------------------------------------------ +func (cmd *MonitorsGetCommand) Name() string { + return COMMAND_NAME_MONITORS_GET +} + +func (cmd *MonitorsGetCommand) CobraCommand() *cobra.Command { + return cmd.cobraCommand +} + +func (cmd *MonitorsGetCommand) Values() interface{} { + return cmd.values +} + +// ------------------------------------------------------------------------------------------------ +// Private methods +// ------------------------------------------------------------------------------------------------ +func (cmd *MonitorsGetCommand) init(factory spi.Factory, monitorsCommand spi.GalasaCommand, commsFlagSet GalasaFlagSet) error { + var err error + + cmd.values = &MonitorsGetCmdValues{} + cmd.cobraCommand, err = cmd.createCobraCmd(factory, monitorsCommand, commsFlagSet.Values().(*CommsFlagSetValues)) + + return err +} + +func (cmd *MonitorsGetCommand) createCobraCmd( + factory spi.Factory, + monitorsCommand spi.GalasaCommand, + commsFlagSetValues *CommsFlagSetValues, +) (*cobra.Command, error) { + + var err error + + monitorsCommandValues := monitorsCommand.Values().(*MonitorsCmdValues) + monitorsGetCobraCmd := &cobra.Command{ + Use: "get", + Short: "Get monitors from the Galasa service", + Long: "Get a list of monitors or a specific monitor from the Galasa service", + Aliases: []string{COMMAND_NAME_MONITORS_GET}, + RunE: func(cobraCommand *cobra.Command, args []string) error { + return cmd.executeMonitorsGet(factory, monitorsCommand.Values().(*MonitorsCmdValues), commsFlagSetValues) + }, + } + + addMonitorNameFlag(monitorsGetCobraCmd, false, monitorsCommandValues) + + formatters := monitors.GetFormatterNamesAsString() + monitorsGetCobraCmd.Flags().StringVar(&cmd.values.outputFormat, "format", "summary", "the output format of the returned monitors. Supported formats are: "+formatters+".") + + monitorsCommand.CobraCommand().AddCommand(monitorsGetCobraCmd) + + return monitorsGetCobraCmd, err +} + +func (cmd *MonitorsGetCommand) executeMonitorsGet( + factory spi.Factory, + monitorsCmdValues *MonitorsCmdValues, + commsFlagSetValues *CommsFlagSetValues, +) error { + + var err error + // Operations on the file system will all be relative to the current folder. + fileSystem := factory.GetFileSystem() + + err = utils.CaptureLog(fileSystem, commsFlagSetValues.logFileName) + if err == nil { + commsFlagSetValues.isCapturingLogs = true + + log.Println("Galasa CLI - Get monitors from the Galasa service") + + env := factory.GetEnvironment() + + var galasaHome spi.GalasaHome + galasaHome, err = utils.NewGalasaHome(fileSystem, env, commsFlagSetValues.CmdParamGalasaHomePath) + if err == nil { + + var commsClient api.APICommsClient + commsClient, err = api.NewAPICommsClient( + commsFlagSetValues.bootstrap, + commsFlagSetValues.maxRetries, + commsFlagSetValues.retryBackoffSeconds, + factory, + galasaHome, + ) + + if err == nil { + + var console = factory.GetStdOutConsole() + byteReader := factory.GetByteReader() + + getMonitorsFunc := func(apiClient *galasaapi.APIClient) error { + return monitors.GetMonitors(monitorsCmdValues.name, cmd.values.outputFormat, console, apiClient, byteReader) + } + err = commsClient.RunAuthenticatedCommandWithRateLimitRetries(getMonitorsFunc) + } + } + } + + return err +} diff --git a/pkg/cmd/monitorsGet_test.go b/pkg/cmd/monitorsGet_test.go new file mode 100644 index 00000000..932388a5 --- /dev/null +++ b/pkg/cmd/monitorsGet_test.go @@ -0,0 +1,63 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package cmd + +import ( + "testing" + + "github.com/galasa-dev/cli/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestCommandListContainsMonitorsGetCommand(t *testing.T) { + /// Given... + factory := utils.NewMockFactory() + commands, _ := NewCommandCollection(factory) + + // When... + monitorsCommand, err := commands.GetCommand(COMMAND_NAME_MONITORS_GET) + assert.Nil(t, err) + + // Then... + assert.NotNil(t, monitorsCommand) + assert.Equal(t, COMMAND_NAME_MONITORS_GET, monitorsCommand.Name()) + assert.NotNil(t, monitorsCommand.Values()) + assert.IsType(t, &MonitorsGetCmdValues{}, monitorsCommand.Values()) +} + +func TestMonitorsGetHelpFlagSetCorrectly(t *testing.T) { + // Given... + factory := utils.NewMockFactory() + commandCollection, _ := setupTestCommandCollection(COMMAND_NAME_MONITORS_GET, factory, t) + + var args []string = []string{"monitors", "get", "--help"} + + // When... + err := commandCollection.Execute(args) + + // Then... + checkOutput("Get a list of monitors or a specific monitor from the Galasa service", "", factory, t) + + assert.Nil(t, err) +} + +func TestMonitorsGetNoFlagsReturnsOk(t *testing.T) { + // Given... + factory := utils.NewMockFactory() + commandCollection, _ := setupTestCommandCollection(COMMAND_NAME_MONITORS_GET, factory, t) + + var args []string = []string{"monitors", "get"} + + // When... + err := commandCollection.Execute(args) + + // Then... + assert.Nil(t, err) + + // Check what the user saw is reasonable. + checkOutput("", "", factory, t) +} + diff --git a/pkg/cmd/monitors_test.go b/pkg/cmd/monitors_test.go new file mode 100644 index 00000000..24cad76c --- /dev/null +++ b/pkg/cmd/monitors_test.go @@ -0,0 +1,59 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package cmd + +import ( + "testing" + + "github.com/galasa-dev/cli/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestCommandListContainsMonitorsCommand(t *testing.T) { + /// Given... + factory := utils.NewMockFactory() + commands, _ := NewCommandCollection(factory) + + // When... + monitorsCommand, err := commands.GetCommand(COMMAND_NAME_MONITORS) + assert.Nil(t, err) + + // Then... + assert.NotNil(t, monitorsCommand) + assert.Equal(t, COMMAND_NAME_MONITORS, monitorsCommand.Name()) + assert.NotNil(t, monitorsCommand.Values()) + assert.IsType(t, &MonitorsCmdValues{}, monitorsCommand.Values()) +} + +func TestMonitorsHelpFlagSetCorrectly(t *testing.T) { + // Given... + factory := utils.NewMockFactory() + + var args []string = []string{"monitors", "--help"} + + // When... + err := Execute(factory, args) + + // Then... + // Check what the user saw is reasonable. + checkOutput("The parent command for operations to manipulate monitors in the Galasa service", "", factory, t) + + assert.Nil(t, err) +} + +func TestMonitorsNoCommandsProducesUsageReport(t *testing.T) { + // Given... + factory := utils.NewMockFactory() + var args []string = []string{"monitors"} + + // When... + err := Execute(factory, args) + + // Then... + assert.Nil(t, err) + + checkOutput("Usage:\n galasactl monitors [command]", "", factory, t) +} diff --git a/pkg/errors/errorMessage.go b/pkg/errors/errorMessage.go index 2cb18afc..d8322492 100644 --- a/pkg/errors/errorMessage.go +++ b/pkg/errors/errorMessage.go @@ -419,6 +419,18 @@ var ( GALASA_ERROR_UPDATE_USER_SERVER_REPORTED_ERROR = NewMessageType("GAL1216E: An attempt to update a user '%s' failed. Unexpected http status code %v received from the server. Error details from the server are: '%s'", 1216, STACK_TRACE_NOT_WANTED) GALASA_ERROR_UPDATE_USER_EXPLANATION_NOT_JSON = NewMessageType("GAL1217E: An attempt to update a user '%s' failed. Unexpected http status code %v received from the server. Error details from the server are not in the json format.", 1217, STACK_TRACE_NOT_WANTED) + // When getting multiple monitors... + GALASA_ERROR_GET_MONITORS_REQUEST_FAILED = NewMessageType("GAL1218E: Failed to get monitors. Sending the get request to the Galasa service failed. Cause is %v", 1218, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_GET_MONITORS_NO_RESPONSE_CONTENT = NewMessageType("GAL1219E: Failed to get monitors. Unexpected http status code %v received from the server.", 1219, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_GET_MONITORS_RESPONSE_BODY_UNREADABLE = NewMessageType("GAL1220E: Failed to get monitors. Unexpected http status code %v received from the server. Error details from the server could not be read. Cause: %s", 1220, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_GET_MONITORS_UNPARSEABLE_CONTENT = NewMessageType("GAL1221E: Failed to get monitors. Unexpected http status code %v received from the server. Error details from the server are not in a valid json format. Cause: '%s'", 1221, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_GET_MONITORS_SERVER_REPORTED_ERROR = NewMessageType("GAL1222E: Failed to get monitors. Unexpected http status code %v received from the server. Error details from the server are: '%s'", 1222, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_GET_MONITORS_EXPLANATION_NOT_JSON = NewMessageType("GAL1223E: Failed to get monitors. Unexpected http status code %v received from the server. Error details from the server are not in the json format.", 1223, STACK_TRACE_NOT_WANTED) + + // Getting a single monitor by name... + GALASA_ERROR_MONITOR_NAME_NOT_FOUND = NewMessageType("GAL1224E: Galasa Monitor named %v is not known on the Galasa service.", 1224, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_INVALID_MONITOR_NAME = NewMessageType("GAL1225E: Invalid monitor name provided. The name provided with the --name flag cannot be empty and must only contain characters in the following ranges: 'a'-'z', 'A'-'Z', '0'-'9', '-' (dash), '_' (underscore).", 1225, STACK_TRACE_NOT_WANTED) + // Warnings... GALASA_WARNING_MAVEN_NO_GALASA_OBR_REPO = NewMessageType("GAL2000W: Warning: Maven configuration file settings.xml should contain a reference to a Galasa repository so that the galasa OBR can be resolved. The official release repository is '%s', and 'pre-release' repository is '%s'", 2000, STACK_TRACE_WANTED) diff --git a/pkg/monitors/monitors.go b/pkg/monitors/monitors.go new file mode 100644 index 00000000..465beefb --- /dev/null +++ b/pkg/monitors/monitors.go @@ -0,0 +1,24 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package monitors + +import ( + "strings" + + galasaErrors "github.com/galasa-dev/cli/pkg/errors" + "github.com/galasa-dev/cli/pkg/utils" +) + +func validateMonitorName(monitorName string) (string, error) { + var err error + monitorName = strings.TrimSpace(monitorName) + + if monitorName == "" || !utils.IsNameValid(monitorName) { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_INVALID_MONITOR_NAME) + } + return monitorName, err +} diff --git a/pkg/monitors/monitorsGet.go b/pkg/monitors/monitorsGet.go new file mode 100644 index 00000000..1534c745 --- /dev/null +++ b/pkg/monitors/monitorsGet.go @@ -0,0 +1,206 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package monitors + +import ( + "context" + "log" + "net/http" + "sort" + "strings" + + "github.com/galasa-dev/cli/pkg/embedded" + galasaErrors "github.com/galasa-dev/cli/pkg/errors" + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/galasa-dev/cli/pkg/monitorsformatter" + "github.com/galasa-dev/cli/pkg/spi" +) + +var ( + formatters = createFormatters() +) + +func GetMonitors( + monitorName string, + format string, + console spi.Console, + apiClient *galasaapi.APIClient, + byteReader spi.ByteReader, +) error { + var err error + var chosenFormatter monitorsformatter.MonitorsFormatter + var monitors []galasaapi.GalasaMonitor + + chosenFormatter, err = validateFormatFlag(format) + if err == nil { + if monitorName != "" { + // The user has provided a monitor name, so try to get that monitor + var monitor *galasaapi.GalasaMonitor + monitor, err = getMonitorByName(monitorName, apiClient, byteReader) + if err == nil { + monitors = append(monitors, *monitor) + } + } else { + // Get all monitors + monitors, err = getMonitorsFromRestApi(apiClient, byteReader) + } + + // If we were able to get the monitors, format them as requested by the user + if err == nil { + var formattedOutput string + formattedOutput, err = chosenFormatter.FormatMonitors(monitors) + if err == nil { + console.WriteString(formattedOutput) + } + } + } + log.Printf("GetMonitors exiting. err is %v\n", err) + return err +} + +func getMonitorByName( + monitorName string, + apiClient *galasaapi.APIClient, + byteReader spi.ByteReader, +) (*galasaapi.GalasaMonitor, error) { + var err error + var monitor *galasaapi.GalasaMonitor + monitorName, err = validateMonitorName(monitorName) + if err == nil { + monitor, err = getMonitorFromRestApi(monitorName, apiClient, byteReader) + if monitor == nil { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_MONITOR_NAME_NOT_FOUND, monitorName) + } + } + + return monitor, err +} + +func getMonitorFromRestApi( + monitorName string, + apiClient *galasaapi.APIClient, + byteReader spi.ByteReader, +) (*galasaapi.GalasaMonitor, error) { + var err error + var httpResponse *http.Response + var context context.Context = context.Background() + var restApiVersion string + var monitor *galasaapi.GalasaMonitor + + restApiVersion, err = embedded.GetGalasactlRestApiVersion() + + if err == nil { + monitor, httpResponse, err = apiClient.MonitorsAPIApi.GetMonitorByName(context, monitorName). + ClientApiVersion(restApiVersion). + Execute() + + if httpResponse != nil { + defer httpResponse.Body.Close() + } + + if err != nil { + if httpResponse == nil { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_GET_MONITORS_REQUEST_FAILED, err.Error()) + } else { + err = galasaErrors.HttpResponseToGalasaError( + httpResponse, + "", + byteReader, + galasaErrors.GALASA_ERROR_GET_MONITORS_NO_RESPONSE_CONTENT, + galasaErrors.GALASA_ERROR_GET_MONITORS_RESPONSE_BODY_UNREADABLE, + galasaErrors.GALASA_ERROR_GET_MONITORS_UNPARSEABLE_CONTENT, + galasaErrors.GALASA_ERROR_GET_MONITORS_SERVER_REPORTED_ERROR, + galasaErrors.GALASA_ERROR_GET_MONITORS_EXPLANATION_NOT_JSON, + ) + } + } + } + return monitor, err +} + +func getMonitorsFromRestApi( + apiClient *galasaapi.APIClient, + byteReader spi.ByteReader, +) ([]galasaapi.GalasaMonitor, error) { + var err error + var httpResponse *http.Response + var context context.Context = context.Background() + var restApiVersion string + var monitors []galasaapi.GalasaMonitor + + restApiVersion, err = embedded.GetGalasactlRestApiVersion() + + if err == nil { + monitors, httpResponse, err = apiClient.MonitorsAPIApi.GetMonitors(context). + ClientApiVersion(restApiVersion). + Execute() + + if httpResponse != nil { + defer httpResponse.Body.Close() + } + + if err != nil { + if httpResponse == nil { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_GET_MONITORS_REQUEST_FAILED, err.Error()) + } else { + err = galasaErrors.HttpResponseToGalasaError( + httpResponse, + "", + byteReader, + galasaErrors.GALASA_ERROR_GET_MONITORS_NO_RESPONSE_CONTENT, + galasaErrors.GALASA_ERROR_GET_MONITORS_RESPONSE_BODY_UNREADABLE, + galasaErrors.GALASA_ERROR_GET_MONITORS_UNPARSEABLE_CONTENT, + galasaErrors.GALASA_ERROR_GET_MONITORS_SERVER_REPORTED_ERROR, + galasaErrors.GALASA_ERROR_GET_MONITORS_EXPLANATION_NOT_JSON, + ) + } + } + } + return monitors, err +} + +func createFormatters() map[string]monitorsformatter.MonitorsFormatter { + formatters := make(map[string]monitorsformatter.MonitorsFormatter, 0) + summaryFormatter := monitorsformatter.NewMonitorsSummaryFormatter() + yamlFormatter := monitorsformatter.NewMonitorsYamlFormatter() + + formatters[summaryFormatter.GetName()] = summaryFormatter + formatters[yamlFormatter.GetName()] = yamlFormatter + + return formatters +} + +func GetFormatterNamesAsString() string { + names := make([]string, 0, len(formatters)) + for name := range formatters { + names = append(names, name) + } + sort.Strings(names) + formatterNames := strings.Builder{} + + for index, formatterName := range names { + + if index != 0 { + formatterNames.WriteString(", ") + } + formatterNames.WriteString("'" + formatterName + "'") + } + + return formatterNames.String() +} + +func validateFormatFlag(outputFormatString string) (monitorsformatter.MonitorsFormatter, error) { + var err error + + chosenFormatter, isPresent := formatters[outputFormatString] + + if !isPresent { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_INVALID_OUTPUT_FORMAT, outputFormatString, GetFormatterNamesAsString()) + } + + return chosenFormatter, err +} diff --git a/pkg/monitors/monitorsGet_test.go b/pkg/monitors/monitorsGet_test.go new file mode 100644 index 00000000..65d6044a --- /dev/null +++ b/pkg/monitors/monitorsGet_test.go @@ -0,0 +1,490 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package monitors + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "testing" + + "github.com/galasa-dev/cli/pkg/api" + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/galasa-dev/cli/pkg/utils" + "github.com/stretchr/testify/assert" +) + +const ( + API_VERSION = "galasa-dev/v1alpha1" +) + +func createMockGalasaMonitor(monitorName string, description string) galasaapi.GalasaMonitor { + monitor := *galasaapi.NewGalasaMonitor() + + monitor.SetApiVersion(API_VERSION) + monitor.SetKind("GalasaResourceCleanupMonitor") + + monitorMetadata := *galasaapi.NewGalasaMonitorMetadata() + monitorMetadata.SetName(monitorName) + + if description != "" { + monitorMetadata.SetDescription(description) + } + + monitorData := *galasaapi.NewGalasaMonitorData() + monitorData.SetIsEnabled(true) + + monitorCleanupData := *galasaapi.NewGalasaMonitorDataResourceCleanupData() + monitorCleanupData.SetStream("myStream") + + monitorFilters := *galasaapi.NewGalasaMonitorDataResourceCleanupDataFilters() + monitorFilters.SetIncludes([]string{ "dev.galasa.*", "*myMonitorClass" }) + monitorFilters.SetExcludes([]string{ "exclude.me", "*exclude.me.too.*" }) + + monitorCleanupData.SetFilters(monitorFilters) + monitorData.SetResourceCleanupData(monitorCleanupData) + + monitor.SetMetadata(monitorMetadata) + monitor.SetData(monitorData) + return monitor +} + +func generateExpectedMonitorYaml(monitorName string, description string, monitorKind string) string { + return fmt.Sprintf(`apiVersion: %s +kind: %s +metadata: + name: %s + description: %s +data: + isEnabled: true + resourceCleanupData: + stream: myStream + filters: + includes: + - dev.galasa.* + - '*myMonitorClass' + excludes: + - exclude.me + - '*exclude.me.too.*'`, API_VERSION, monitorKind, monitorName, description) +} + +func TestCanGetAMonitorByName(t *testing.T) { + // Given... + monitorName := "customManagerCleanup" + description := "my custom cleanup monitor" + outputFormat := "summary" + + // Create the mock monitor to return + monitor := createMockGalasaMonitor(monitorName, description) + monitorBytes, _ := json.Marshal(monitor) + monitorJson := string(monitorBytes) + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors/" + monitorName, http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) + writer.Write([]byte(monitorJson)) + } + + interactions := []utils.HttpInteraction{ + getMonitorInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + expectedOutput := +`name kind is-enabled +customManagerCleanup GalasaResourceCleanupMonitor true + +Total:1 +` + assert.Nil(t, err, "GetMonitors returned an unexpected error") + assert.Equal(t, expectedOutput, console.ReadText()) +} + +func TestCanGetAMonitorByNameInYamlFormat(t *testing.T) { + // Given... + monitorName := "cleanupMonitor" + description := "my custom cleanup monitor" + outputFormat := "yaml" + + // Create the mock monitor to return + monitor := createMockGalasaMonitor(monitorName, description) + monitorBytes, _ := json.Marshal(monitor) + monitorJson := string(monitorBytes) + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors/" + monitorName, http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) + writer.Write([]byte(monitorJson)) + } + + interactions := []utils.HttpInteraction{ + getMonitorInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + expectedOutput := generateExpectedMonitorYaml(monitorName, description, "GalasaResourceCleanupMonitor") + "\n" + assert.Nil(t, err, "GetMonitors returned an unexpected error") + assert.Equal(t, expectedOutput, console.ReadText()) +} + +func TestGetAMonitorWithBlankNameDisplaysError(t *testing.T) { + // Given... + monitorName := " " + outputFormat := "summary" + + // The client-side validation should fail, so no HTTP interactions will be performed + interactions := []utils.HttpInteraction{} + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + assert.NotNil(t, err, "GetMonitors did not return an error as expected") + consoleOutputText := err.Error() + assert.Contains(t, consoleOutputText, "GAL1225E") + assert.Contains(t, consoleOutputText, " Invalid monitor name provided") +} + +func TestGetNonExistantMonitorDisplaysError(t *testing.T) { + // Given... + nonExistantMonitor := "monitorDoesNotExist123" + outputFormat := "summary" + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors/" + nonExistantMonitor, http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusNotFound) + writer.Write([]byte(`{ "error_message": "No such monitor exists" }`)) + } + + + interactions := []utils.HttpInteraction{ getMonitorInteraction } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + nonExistantMonitor, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + assert.NotNil(t, err, "MonitorsGet did not return an error but it should have") + consoleOutputText := err.Error() + assert.Contains(t, consoleOutputText, nonExistantMonitor) + assert.Contains(t, consoleOutputText, "GAL1224E") +} + +func TestCanGetAllMonitorsOk(t *testing.T) { + // Given... + // Don't provide a monitor name so that we can get all monitors + monitorName := "" + outputFormat := "summary" + + // Create the mock monitor to return + monitors := make([]galasaapi.GalasaMonitor, 0) + monitor1Name := "monitor1" + monitor2Name := "monitor2" + description1 := "my first cleanup monitor" + description2 := "my other cleanup monitor" + monitor1 := createMockGalasaMonitor(monitor1Name, description1) + monitor2 := createMockGalasaMonitor(monitor2Name, description2) + + monitors = append(monitors, monitor1, monitor2) + monitorsBytes, _ := json.Marshal(monitors) + monitorsJson := string(monitorsBytes) + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors", http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) + writer.Write([]byte(monitorsJson)) + } + + interactions := []utils.HttpInteraction{ + getMonitorInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + expectedOutput := +`name kind is-enabled +monitor1 GalasaResourceCleanupMonitor true +monitor2 GalasaResourceCleanupMonitor true + +Total:2 +` + assert.Nil(t, err, "GetMonitors returned an unexpected error") + assert.Equal(t, expectedOutput, console.ReadText()) +} + +func TestGetMonitorsWithUnknownFormatDisplaysError(t *testing.T) { + // Given... + monitorName := "" + outputFormat := "UNKNOWN FORMAT!" + + // The client-side validation should fail, so no HTTP interactions will be performed + interactions := []utils.HttpInteraction{} + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + assert.NotNil(t, err, "GetMonitors did not return an error as expected") + consoleOutputText := err.Error() + assert.Contains(t, consoleOutputText, "GAL1067E") + assert.Contains(t, consoleOutputText, "Unsupported value 'UNKNOWN FORMAT!'") + assert.Contains(t, consoleOutputText, "'summary', 'yaml'") +} + +func TestGetAllMonitorsFailsWithNoExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { + // Given... + monitorName := "" + outputFormat := "summary" + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors", http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusInternalServerError) + } + + interactions := []utils.HttpInteraction{ + getMonitorInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + assert.NotNil(t, err, "MonitorsGet did not return an error but it should have") + errorMsg := err.Error() + assert.Contains(t, errorMsg , "GAL1219E") +} + +func TestGetAllMonitorsFailsWithNonJsonContentTypeExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { + // Given... + monitorName := "" + outputFormat := "summary" + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors", http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusInternalServerError) + writer.Header().Set("Content-Type", "application/notJsonOnPurpose") + writer.Write([]byte("something not json but non-zero-length.")) + } + + interactions := []utils.HttpInteraction{ + getMonitorInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + assert.NotNil(t, err, "MonitorsGet did not return an error but it should have") + errorMsg := err.Error() + assert.Contains(t, errorMsg, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, errorMsg, "GAL1223E") + assert.Contains(t, errorMsg, "Error details from the server are not in the json format") +} + +func TestGetAllMonitorsFailsWithBadlyFormedJsonContentExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { + // Given... + monitorName := "" + outputFormat := "summary" + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors", http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte(`{ "this": "isBadJson because it doesnt end in a close braces" `)) + } + + interactions := []utils.HttpInteraction{ + getMonitorInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReader() + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + assert.NotNil(t, err, "MonitorsGet did not return an error but it should have") + errorMsg := err.Error() + assert.Contains(t, errorMsg, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, errorMsg, "GAL1221E") + assert.Contains(t, errorMsg, "Error details from the server are not in a valid json format") + assert.Contains(t, errorMsg, "Cause: 'unexpected end of JSON input'") +} + +func TestGetAllMonitorsFailsWithFailureToReadResponseBodyGivesCorrectMessage(t *testing.T) { + // Given... + monitorName := "" + outputFormat := "summary" + + // Create the expected HTTP interactions with the API server + getMonitorInteraction := utils.NewHttpInteraction("/monitors", http.MethodGet) + getMonitorInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte(`{}`)) + } + + interactions := []utils.HttpInteraction{ + getMonitorInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockByteReader := utils.NewMockByteReaderAsMock(true) + + // When... + err := GetMonitors( + monitorName, + outputFormat, + console, + apiClient, + mockByteReader) + + // Then... + assert.NotNil(t, err, "MonitorsGet returned an unexpected error") + errorMsg := err.Error() + assert.Contains(t, errorMsg, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, errorMsg, "GAL1220E") + assert.Contains(t, errorMsg, "Error details from the server could not be read") +} diff --git a/pkg/monitorsformatter/monitorsFormatter.go b/pkg/monitorsformatter/monitorsFormatter.go new file mode 100644 index 00000000..894533ca --- /dev/null +++ b/pkg/monitorsformatter/monitorsFormatter.go @@ -0,0 +1,32 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package monitorsformatter + +import ( + "github.com/galasa-dev/cli/pkg/galasaapi" +) + +// Displays monitors in the following format: +// name kind: is-enabled +// _system-certificate-monitor GalasaCertificateMonitor true +// _system-resource-cleanup-monitor GalasaResourceCleanupMonitor true +// myCustomResourceMonitor GalasaResourceCleanupMonitor true +// +// Total: 3 +// ----------------------------------------------------- +// MonitorsFormatter - implementations can take a collection of monitors +// and turn them into a string for display to the user. +const ( + HEADER_MONITOR_NAME = "name" + HEADER_MONITOR_KIND = "kind" + HEADER_MONITOR_IS_ENABLED = "is-enabled" +) + +type MonitorsFormatter interface { + FormatMonitors(monitors []galasaapi.GalasaMonitor) (string, error) + GetName() string +} diff --git a/pkg/monitorsformatter/summaryFormatter.go b/pkg/monitorsformatter/summaryFormatter.go new file mode 100644 index 00000000..37880f8e --- /dev/null +++ b/pkg/monitorsformatter/summaryFormatter.go @@ -0,0 +1,69 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package monitorsformatter + +import ( + "strconv" + "strings" + + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/galasa-dev/cli/pkg/utils" +) + +// ----------------------------------------------------- +// Summary format. +const ( + SUMMARY_FORMATTER_NAME = "summary" +) + +type MonitorsSummaryFormatter struct { +} + +func NewMonitorsSummaryFormatter() MonitorsFormatter { + return new(MonitorsSummaryFormatter) +} + +func (*MonitorsSummaryFormatter) GetName() string { + return SUMMARY_FORMATTER_NAME +} + +func (*MonitorsSummaryFormatter) FormatMonitors(monitors []galasaapi.GalasaMonitor) (string, error) { + var result string + var err error = nil + buff := strings.Builder{} + total := len(monitors) + + if total > 0 { + var table [][]string + + var headers = []string{ + HEADER_MONITOR_NAME, + HEADER_MONITOR_KIND, + HEADER_MONITOR_IS_ENABLED, + } + + table = append(table, headers) + for _, monitor := range monitors { + var line []string + name := monitor.Metadata.GetName() + kind := monitor.GetKind() + isEnabled := monitor.Data.GetIsEnabled() + + line = append(line, name, kind, strconv.FormatBool(isEnabled)) + table = append(table, line) + } + + columnLengths := utils.CalculateMaxLengthOfEachColumn(table) + utils.WriteFormattedTableToStringBuilder(table, &buff, columnLengths) + + buff.WriteString("\n") + + } + buff.WriteString("Total:" + strconv.Itoa(total) + "\n") + + result = buff.String() + return result, err +} diff --git a/pkg/monitorsformatter/summaryFormatter_test.go b/pkg/monitorsformatter/summaryFormatter_test.go new file mode 100644 index 00000000..e3fc5d1d --- /dev/null +++ b/pkg/monitorsformatter/summaryFormatter_test.go @@ -0,0 +1,110 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package monitorsformatter + +import ( + "testing" + + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/stretchr/testify/assert" +) + +const ( + API_VERSION = "galasa-dev/v1alpha1" +) + +func createTestMonitors() []galasaapi.GalasaMonitor { + + monitor1 := galasaapi.NewGalasaMonitor() + monitor1.SetApiVersion(API_VERSION) + monitor1.SetKind("GalasaResourceCleanupMonitor") + + monitor1Metadata := *galasaapi.NewGalasaMonitorMetadata() + monitor1Metadata.SetName("monitor1Name") + monitor1Metadata.SetDescription("monitor1Description") + monitor1.Metadata = &monitor1Metadata + + monitor1Data := *galasaapi.NewGalasaMonitorData() + monitor1Data.SetIsEnabled(true) + monitor1.Data = &monitor1Data + + monitor1ResourceCleanupData := *galasaapi.NewGalasaMonitorDataResourceCleanupData() + monitor1ResourceCleanupData.SetStream("monitor1Stream") + + monitor1Data.ResourceCleanupData = &monitor1ResourceCleanupData + + monitor1Filters := *galasaapi.NewGalasaMonitorDataResourceCleanupDataFilters() + monitor1Filters.SetIncludes([]string{ "dev.galasa.*", "my.company.*" }) + monitor1Filters.SetExcludes([]string{ "dev.galasa.core.*", "dev.galasa.docker.*" }) + + monitor1ResourceCleanupData.Filters = &monitor1Filters + + monitor2 := galasaapi.NewGalasaMonitor() + monitor2.SetApiVersion(API_VERSION) + monitor2.SetKind("GalasaResourceCleanupMonitor") + + monitor2Metadata := *galasaapi.NewGalasaMonitorMetadata() + monitor2Metadata.SetName("monitor2Name") + monitor2Metadata.SetDescription("monitor2Description") + monitor2.Metadata = &monitor2Metadata + + monitor2Data := *galasaapi.NewGalasaMonitorData() + monitor2Data.SetIsEnabled(false) + monitor2.Data = &monitor2Data + + monitor2ResourceCleanupData := *galasaapi.NewGalasaMonitorDataResourceCleanupData() + monitor2ResourceCleanupData.SetStream("monitor2Stream") + + monitor2Data.ResourceCleanupData = &monitor1ResourceCleanupData + + monitor2Filters := *galasaapi.NewGalasaMonitorDataResourceCleanupDataFilters() + monitor2Filters.SetIncludes([]string{ "*" }) + monitor2Filters.SetExcludes([]string{ "my.company.*" }) + + monitor2ResourceCleanupData.Filters = &monitor2Filters + + monitors := []galasaapi.GalasaMonitor{ *monitor1, *monitor2 } + return monitors +} + +func TestMonitorsSummaryFormatterHasCorrectName(t *testing.T) { + formatter := NewMonitorsSummaryFormatter() + assert.Equal(t, formatter.GetName(), "summary") +} + +func TestMonitorsSummaryFormatterValidDataReturnsTotalCountTwo(t *testing.T) { + // Given... + formatter := NewMonitorsSummaryFormatter() + monitors := createTestMonitors() + + // When... + actualFormattedOutput, err := formatter.FormatMonitors(monitors) + + // Then... + assert.Nil(t, err) + expectedFormattedOutput := +`name kind is-enabled +monitor1Name GalasaResourceCleanupMonitor true +monitor2Name GalasaResourceCleanupMonitor false + +Total:2 +` + assert.Equal(t, expectedFormattedOutput, actualFormattedOutput) +} + +func TestMonitorsSummaryFormatterNoDataReturnsTotalCountAllZeros(t *testing.T) { + // Given... + formatter := NewMonitorsSummaryFormatter() + monitors := make([]galasaapi.GalasaMonitor, 0) + + // When... + actualFormattedOutput, err := formatter.FormatMonitors(monitors) + + // Then... + assert.Nil(t, err) + expectedFormattedOutput := "Total:0\n" + assert.Equal(t, expectedFormattedOutput, actualFormattedOutput) +} diff --git a/pkg/monitorsformatter/yamlFormatter.go b/pkg/monitorsformatter/yamlFormatter.go new file mode 100644 index 00000000..d1207074 --- /dev/null +++ b/pkg/monitorsformatter/yamlFormatter.go @@ -0,0 +1,53 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package monitorsformatter + +import ( + "strings" + + "github.com/galasa-dev/cli/pkg/galasaapi" + "gopkg.in/yaml.v3" +) + +const ( + YAML_FORMATTER_NAME = "yaml" +) + +type MonitorsYamlFormatter struct { +} + +func NewMonitorsYamlFormatter() MonitorsFormatter { + return new(MonitorsYamlFormatter) +} + +func (*MonitorsYamlFormatter) GetName() string { + return YAML_FORMATTER_NAME +} + +func (*MonitorsYamlFormatter) FormatMonitors(monitors []galasaapi.GalasaMonitor) (string, error) { + var err error + buff := strings.Builder{} + + for index, monitor := range monitors { + content := "" + + if index > 0 { + content += "---\n" + } + + var yamlRepresentationBytes []byte + yamlRepresentationBytes, err = yaml.Marshal(monitor) + if err == nil { + yamlStr := string(yamlRepresentationBytes) + content += yamlStr + } + + buff.WriteString(content) + } + + result := buff.String() + return result, err +} diff --git a/pkg/monitorsformatter/yamlFormatter_test.go b/pkg/monitorsformatter/yamlFormatter_test.go new file mode 100644 index 00000000..02ecddae --- /dev/null +++ b/pkg/monitorsformatter/yamlFormatter_test.go @@ -0,0 +1,80 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package monitorsformatter + +import ( + "testing" + + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/stretchr/testify/assert" +) + +func TestMonitorsYamlFormatterHasCorrectName(t *testing.T) { + formatter := NewMonitorsYamlFormatter() + assert.Equal(t, formatter.GetName(), "yaml") +} + +func TestMonitorsYamlFormatterValidData(t *testing.T) { + // Given... + formatter := NewMonitorsYamlFormatter() + monitors := createTestMonitors() + + // When... + actualFormattedOutput, err := formatter.FormatMonitors(monitors) + + // Then... + assert.Nil(t, err) + expectedFormattedOutput := +`apiVersion: galasa-dev/v1alpha1 +kind: GalasaResourceCleanupMonitor +metadata: + name: monitor1Name + description: monitor1Description +data: + isEnabled: true + resourceCleanupData: + stream: monitor1Stream + filters: + includes: + - dev.galasa.* + - my.company.* + excludes: + - dev.galasa.core.* + - dev.galasa.docker.* +--- +apiVersion: galasa-dev/v1alpha1 +kind: GalasaResourceCleanupMonitor +metadata: + name: monitor2Name + description: monitor2Description +data: + isEnabled: false + resourceCleanupData: + stream: monitor1Stream + filters: + includes: + - dev.galasa.* + - my.company.* + excludes: + - dev.galasa.core.* + - dev.galasa.docker.* +` + assert.Equal(t, expectedFormattedOutput, actualFormattedOutput) +} + +func TestMonitorsYamlFormatterNoDataReturnsTotalCountAllZeros(t *testing.T) { + // Given... + formatter := NewMonitorsYamlFormatter() + monitors := make([]galasaapi.GalasaMonitor, 0) + + // When... + actualFormattedOutput, err := formatter.FormatMonitors(monitors) + + // Then... + assert.Nil(t, err) + expectedFormattedOutput := "" + assert.Equal(t, expectedFormattedOutput, actualFormattedOutput) +}