diff --git a/application/cli/cli.go b/application/cli/cli.go index 96fc7cb..2f2aebf 100644 --- a/application/cli/cli.go +++ b/application/cli/cli.go @@ -41,6 +41,7 @@ func GetJfrogApplicationCli() components.App { version.GetPromoteAppVersionCommand(appContext), application.GetCreateAppCommand(appContext), application.GetUpdateAppCommand(appContext), + application.GetDeleteAppCommand(appContext), }, ) return appEntity diff --git a/application/commands/application/delete_app_cmd.go b/application/commands/application/delete_app_cmd.go new file mode 100644 index 0000000..e9f331e --- /dev/null +++ b/application/commands/application/delete_app_cmd.go @@ -0,0 +1,73 @@ +package application + +import ( + "github.com/jfrog/jfrog-cli-application/application/app" + commonCLiCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + pluginsCommon "github.com/jfrog/jfrog-cli-core/v2/plugins/common" + + "github.com/jfrog/jfrog-cli-application/application/commands" + "github.com/jfrog/jfrog-cli-application/application/commands/utils" + "github.com/jfrog/jfrog-cli-application/application/common" + "github.com/jfrog/jfrog-cli-application/application/service" + "github.com/jfrog/jfrog-cli-application/application/service/applications" + "github.com/jfrog/jfrog-cli-core/v2/plugins/components" + coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" +) + +type deleteAppCommand struct { + serverDetails *coreConfig.ServerDetails + applicationService applications.ApplicationService + applicationKey string +} + +func (dac *deleteAppCommand) Run() error { + ctx, err := service.NewContext(*dac.serverDetails) + if err != nil { + return err + } + + return dac.applicationService.DeleteApplication(ctx, dac.applicationKey) +} + +func (dac *deleteAppCommand) ServerDetails() (*coreConfig.ServerDetails, error) { + return dac.serverDetails, nil +} + +func (dac *deleteAppCommand) CommandName() string { + return commands.DeleteApp +} + +func (dac *deleteAppCommand) prepareAndRunCommand(ctx *components.Context) error { + if len(ctx.Arguments) != 1 { + return pluginsCommon.WrongNumberOfArgumentsHandler(ctx) + } + + dac.applicationKey = ctx.Arguments[0] + + var err error + dac.serverDetails, err = utils.ServerDetailsByFlags(ctx) + if err != nil { + return err + } + + return commonCLiCommands.Exec(dac) +} + +func GetDeleteAppCommand(appContext app.Context) components.Command { + cmd := &deleteAppCommand{ + applicationService: appContext.GetApplicationService(), + } + return components.Command{ + Name: "delete", + Description: "Delete an application", + Category: common.CategoryApplication, + Arguments: []components.Argument{ + { + Name: "application-key", + Description: "The key of the application to delete", + Optional: false, + }, + }, + Action: cmd.prepareAndRunCommand, + } +} diff --git a/application/commands/application/delete_app_cmd_test.go b/application/commands/application/delete_app_cmd_test.go new file mode 100644 index 0000000..7edac35 --- /dev/null +++ b/application/commands/application/delete_app_cmd_test.go @@ -0,0 +1,76 @@ +package application + +import ( + "errors" + "flag" + "testing" + + mockapps "github.com/jfrog/jfrog-cli-application/application/service/applications/mocks" + "github.com/jfrog/jfrog-cli-core/v2/plugins/components" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" + "go.uber.org/mock/gomock" +) + +func TestDeleteAppCommand_Run(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serverDetails := &config.ServerDetails{Url: "https://example.com"} + appKey := "app-key" + + mockAppService := mockapps.NewMockApplicationService(ctrl) + mockAppService.EXPECT().DeleteApplication(gomock.Any(), appKey).Return(nil).Times(1) + + cmd := &deleteAppCommand{ + applicationService: mockAppService, + serverDetails: serverDetails, + applicationKey: appKey, + } + + err := cmd.Run() + assert.NoError(t, err) +} + +func TestDeleteAppCommand_Run_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serverDetails := &config.ServerDetails{Url: "https://example.com"} + appKey := "app-key" + + mockAppService := mockapps.NewMockApplicationService(ctrl) + mockAppService.EXPECT().DeleteApplication(gomock.Any(), appKey).Return(errors.New("failed to delete application. Status code: 500")).Times(1) + + cmd := &deleteAppCommand{ + applicationService: mockAppService, + serverDetails: serverDetails, + applicationKey: appKey, + } + + err := cmd.Run() + assert.Error(t, err) + assert.Equal(t, "failed to delete application. Status code: 500", err.Error()) +} + +func TestDeleteAppCommand_WrongNumberOfArguments(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ctx := cli.NewContext(app, set, nil) + + mockAppService := mockapps.NewMockApplicationService(ctrl) + cmd := &deleteAppCommand{ + applicationService: mockAppService, + } + + // Test with no arguments + context, err := components.ConvertContext(ctx) + assert.NoError(t, err) + + err = cmd.prepareAndRunCommand(context) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Wrong number of arguments") +} diff --git a/application/commands/flags.go b/application/commands/flags.go index 9ea7426..316e832 100644 --- a/application/commands/flags.go +++ b/application/commands/flags.go @@ -13,6 +13,7 @@ const ( PromoteAppVersion = "promote-app-version" CreateApp = "create-app" UpdateApp = "update-app" + DeleteApp = "delete-app" ) const ( diff --git a/application/http/http_client.go b/application/http/http_client.go index bc476cc..051d4c4 100644 --- a/application/http/http_client.go +++ b/application/http/http_client.go @@ -26,6 +26,7 @@ type AppHttpClient interface { Post(path string, requestBody interface{}) (resp *http.Response, body []byte, err error) Get(path string) (resp *http.Response, body []byte, err error) Patch(path string, requestBody interface{}) (resp *http.Response, body []byte, err error) + Delete(path string) (resp *http.Response, body []byte, err error) } type appHttpClient struct { @@ -138,6 +139,16 @@ func (c *appHttpClient) toJsonBytes(payload interface{}) ([]byte, error) { return jsonBytes, nil } +func (c *appHttpClient) Delete(path string) (resp *http.Response, body []byte, err error) { + url, err := utils.BuildUrl(c.serverDetails.Url, appTrustApiPath+path, nil) + if err != nil { + return nil, nil, err + } + + log.Debug("Sending DELETE request to:", url) + return c.client.SendDelete(url, nil, c.getJsonHttpClientDetails()) +} + func (c *appHttpClient) getJsonHttpClientDetails() *httputils.HttpClientDetails { httpClientDetails := c.authDetails.CreateHttpClientDetails() httpClientDetails.SetContentTypeApplicationJson() diff --git a/application/service/applications/application_service.go b/application/service/applications/application_service.go index ec51544..bb1512a 100644 --- a/application/service/applications/application_service.go +++ b/application/service/applications/application_service.go @@ -15,6 +15,7 @@ import ( type ApplicationService interface { CreateApplication(ctx service.Context, requestBody *model.AppDescriptor) error UpdateApplication(ctx service.Context, requestBody *model.AppDescriptor) error + DeleteApplication(ctx service.Context, applicationKey string) error } type applicationService struct{} @@ -53,3 +54,19 @@ func (as *applicationService) UpdateApplication(ctx service.Context, requestBody fmt.Println(string(responseBody)) return nil } + +func (as *applicationService) DeleteApplication(ctx service.Context, applicationKey string) error { + endpoint := fmt.Sprintf("/v1/applications/%s", applicationKey) + response, responseBody, err := ctx.GetHttpClient().Delete(endpoint) + if err != nil { + return err + } + + if response.StatusCode != http.StatusNoContent { + return errorutils.CheckErrorf("failed to delete application. Status code: %d.\n%s", + response.StatusCode, responseBody) + } + + fmt.Println("Application deleted successfully") + return nil +}