Skip to content

Commit

Permalink
feat(cli): implement share-add command (reanahub#153)
Browse files Browse the repository at this point in the history
Adds a new command to the CLI to share a workflow with a user.

Closes reanahub/reana-client#680
  • Loading branch information
DaanRosendal committed Mar 18, 2024
1 parent 3b7da05 commit a07306c
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 0 deletions.
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ The list of contributors in alphabetical order:

- [Audrius Mecionis](https://orcid.org/0000-0002-3759-1663)
- [Bruno Rosendo](https://orcid.org/0000-0002-0923-3148)
- [Daan Rosendal](https://orcid.org/0000-0002-3447-9000)
- [Giuseppe Steduto](https://orcid.org/0009-0002-1258-8553)
- [Marco Donadoni](https://orcid.org/0000-0003-2922-5505)
- [Tibor Simko](https://orcid.org/0000-0001-7202-5803)
6 changes: 6 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ func NewRootCmd() *cobra.Command {
newStatusCmd(),
},
},
{
Message: "Workflow sharing commands:",
Commands: []*cobra.Command{
newShareAddCmd(),
},
},
{
Message: "Workspace interactive commands:",
Commands: []*cobra.Command{
Expand Down
145 changes: 145 additions & 0 deletions cmd/share_add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
This file is part of REANA.
Copyright (C) 2023 CERN.
REANA is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
*/

package cmd

import (
"fmt"
"reanahub/reana-client-go/client"
"reanahub/reana-client-go/client/operations"
"reanahub/reana-client-go/pkg/config"
"reanahub/reana-client-go/pkg/displayer"
"reanahub/reana-client-go/pkg/errorhandler"
"reanahub/reana-client-go/pkg/validator"
"strings"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

const shareAddDesc = `Share a workflow with other users (read-only).
The ` + "`share-add`" + ` command allows sharing a workflow with other users.
The users will be able to view the workflow but not modify it.
Examples:
$ reana-client share-add -w myanalysis.42 --user bob@cern.ch
$ reana-client share-add -w myanalysis.42 --user bob@cern.ch
--user cecile@cern.ch --message "Please review my analysis"
--valid-until 2024-12-31
`

type shareAddOptions struct {
token string
workflow string
users []string
message string
validUntil string
}

// newShareAddCmd creates a command to share a workflow with other users.
func newShareAddCmd() *cobra.Command {
o := &shareAddOptions{}

cmd := &cobra.Command{
Use: "share-add",
Short: "Share a workflow with other users (read-only).",
Long: shareAddDesc,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validator.ValidateAtLeastOne(
cmd.Flags(), []string{"user"},
); err != nil {
return fmt.Errorf("%s\n%s", err.Error(), cmd.UsageString())
}
return o.run(cmd)
},
}

f := cmd.Flags()
f.StringVarP(
&o.workflow,
"workflow",
"w",
"",
"Name or UUID of the workflow. Overrides value of REANA_WORKON environment variable.",
)
f.StringVarP(&o.token, "access-token", "t", "", "Access token of the current user.")
f.StringSliceVarP(&o.users, "user", "u", []string{}, `Users to share the workflow with.`)
f.StringVarP(&o.message, "message", "m", "", `Optional message that is sent to the
user(s) with the sharing invitation.`)
f.StringVar(&o.validUntil, "valid-until", "", `Optional date when access to the
workflow will expire for the given
user(s) (format: YYYY-MM-DD).`)
// Remove -h shorthand
cmd.PersistentFlags().BoolP("help", "h", false, "Help for share-add")

return cmd
}

func (o *shareAddOptions) run(cmd *cobra.Command) error {
shareAddParams := operations.NewShareWorkflowParams()
shareAddParams.SetAccessToken(&o.token)
shareAddParams.SetWorkflowIDOrName(o.workflow)
if o.message != "" {
shareAddParams.SetMessage(&o.message)
}

if o.validUntil != "" {
shareAddParams.SetValidUntil(&o.validUntil)
}

api, err := client.ApiClient()
if err != nil {
return err
}

shareErrors := []string{}
sharedUsers := []string{}

for _, user := range o.users {
log.Infof("Sharing workflow %s with user %s", o.workflow, user)

shareAddParams.SetUserEmailToShareWith(user)
_, err := api.Operations.ShareWorkflow(shareAddParams)

if err != nil {
err := errorhandler.HandleApiError(err)
shareErrors = append(
shareErrors,
fmt.Sprintf("Failed to share %s with %s: %s", o.workflow, user, err.Error()),
)
} else {
sharedUsers = append(sharedUsers, user)
}
}

if len(sharedUsers) > 0 {
displayer.DisplayMessage(
fmt.Sprintf(
"%s is now read-only shared with %s",
o.workflow,
strings.Join(sharedUsers, ", "),
),
displayer.Success,
false,
cmd.OutOrStdout(),
)
}
if len(shareErrors) > 0 {
for _, err := range shareErrors {
displayer.DisplayMessage(err, displayer.Error, false, cmd.OutOrStdout())
}

return config.EmptyError
}

return nil
}
72 changes: 72 additions & 0 deletions cmd/share_add_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
This file is part of REANA.
Copyright (C) 2023 CERN.
REANA is free software; you can redistribute it and/or modify it under the terms
of the MIT License; see LICENSE file for more details.
*/

package cmd

import (
"fmt"
"net/http"
"testing"
)

var shareAddPathTemplate = "/api/workflows/%s/share"

func TestShareAdd(t *testing.T) {
workflowName := "my_workflow"
tests := map[string]TestCmdParams{
"default": {
serverResponses: map[string]ServerResponse{
fmt.Sprintf(shareAddPathTemplate, workflowName): {
statusCode: http.StatusOK,
},
},
args: []string{"-w", workflowName, "--user", "bob@cern.ch"},
expected: []string{
"my_workflow is now read-only shared with bob@cern.ch",
},
},
"with message and valid-until": {
serverResponses: map[string]ServerResponse{
fmt.Sprintf(shareAddPathTemplate, workflowName): {
statusCode: http.StatusOK,
},
},
args: []string{
"-w", workflowName,
"--user", "bob@cern.ch",
"--message", "Please review my analysis",
"--valid-until", "2024-12-31",
},
expected: []string{
"my_workflow is now read-only shared with bob@cern.ch",
},
},
"invalid workflow": {
serverResponses: map[string]ServerResponse{
fmt.Sprintf(shareAddPathTemplate, "invalid"): {
statusCode: http.StatusNotFound,
responseFile: "common_invalid_workflow.json",
},
},
args: []string{
"-w", "invalid",
"--user", "bob@cern.ch",
},
expected: []string{
"REANA_WORKON is set to invalid, but that workflow does not exist.",
},
},
}

for name, params := range tests {
t.Run(name, func(t *testing.T) {
params.cmd = "share-add"
testCmdRun(t, params)
})
}
}

0 comments on commit a07306c

Please sign in to comment.