Skip to content

Commit 8f700a7

Browse files
committed
OSD-24127: New accessrequest subcommand used to manage access requests used by the lockbox feature
1 parent d7482fe commit 8f700a7

File tree

17 files changed

+1395
-14
lines changed

17 files changed

+1395
-14
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ mock-gen:
115115
mockgen -destination=./pkg/cli/session/mocks/sessionMock.go -package=mocks github.com/openshift/backplane-cli/pkg/cli/session BackplaneSessionInterface
116116
mockgen -destination=./pkg/utils/mocks/shellCheckerMock.go -package=mocks github.com/openshift/backplane-cli/pkg/utils ShellCheckerInterface
117117
mockgen -destination=./pkg/pagerduty/mocks/clientMock.go -package=mocks github.com/openshift/backplane-cli/pkg/pagerduty PagerDutyClient
118+
mockgen -destination=./pkg/utils/mocks/jiraMock.go -package=mocks github.com/openshift/backplane-cli/pkg/utils IssueServiceInterface
118119

119120
.PHONY: build-image
120121
build-image:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package accessrequest
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
func NewAccessRequestCmd() *cobra.Command {
8+
cmd := &cobra.Command{
9+
Use: "accessrequest",
10+
Aliases: []string{"accessRequest", "accessrequest", "accessrequests"},
11+
Short: "Manages access requests for clusters on which access protection is enabled",
12+
SilenceUsage: true,
13+
}
14+
15+
// cluster-id Flag
16+
cmd.PersistentFlags().StringP("cluster-id", "c", "", "Cluster ID could be cluster name, id or external-id")
17+
18+
cmd.AddCommand(newCreateAccessRequestCmd())
19+
cmd.AddCommand(newGetAccessRequestCmd())
20+
cmd.AddCommand(newExpireAccessRequestCmd())
21+
22+
return cmd
23+
}
24+
25+
func init() {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package accessrequest
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
"github.com/openshift/backplane-cli/pkg/accessrequest"
10+
11+
ocmcli "github.com/openshift-online/ocm-cli/pkg/ocm"
12+
"github.com/openshift/backplane-cli/pkg/login"
13+
"github.com/openshift/backplane-cli/pkg/utils"
14+
logger "github.com/sirupsen/logrus"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
var (
19+
options struct {
20+
reason string
21+
notificationIssueID string
22+
pendingDuration time.Duration
23+
approvalDuration time.Duration
24+
}
25+
)
26+
27+
// newCreateAccessRequestCmd returns cobra command
28+
func newCreateAccessRequestCmd() *cobra.Command {
29+
cmd := &cobra.Command{
30+
Use: "create",
31+
Short: "Creates a new pending access request",
32+
Args: cobra.ExactArgs(0),
33+
SilenceUsage: true,
34+
SilenceErrors: true,
35+
RunE: runCreateAccessRequest,
36+
}
37+
38+
cmd.Flags().StringVarP(
39+
&options.reason,
40+
"reason",
41+
"r",
42+
"",
43+
"Reason/justification passed through the access request to the customer. "+
44+
"Reason will be read from the kube context (unless --cluster-id is set) or prompted if the option is not set.")
45+
46+
cmd.Flags().StringVarP(
47+
&options.notificationIssueID,
48+
"notification-issue",
49+
"n",
50+
"",
51+
"JIRA issue used for notifications when the access request is approved or denied. "+
52+
"Issue needs to belong to the OHSS project on production and to the SDAINT project for staging & integration. "+
53+
"Issue will automatically be created in the proper project if the option is not set.")
54+
55+
cmd.Flags().DurationVarP(
56+
&options.approvalDuration,
57+
"approval-duration",
58+
"d",
59+
8*time.Hour,
60+
"The maximal period of time during which the access request can stay approved")
61+
62+
return cmd
63+
}
64+
65+
func retrieveOrPromptReason(cmd *cobra.Command) string {
66+
if utils.CheckValidPrompt() {
67+
clusterKey, err := cmd.Flags().GetString("cluster-id")
68+
69+
if err == nil && clusterKey == "" {
70+
config, err := utils.ReadKubeconfigRaw()
71+
72+
if err == nil {
73+
reasons := login.GetElevateContextReasons(config)
74+
for _, reason := range reasons {
75+
if reason != "" {
76+
fmt.Printf("Reason for elevations read from the kube config: %s\n", reason)
77+
if strings.ToLower(utils.AskQuestionFromPrompt("Do you want to use this as the reason/justification for the access request to create (Y/n)? ")) != "n" {
78+
return reason
79+
}
80+
break
81+
}
82+
}
83+
} else {
84+
logger.Warnf("won't extract the elevation reason from the kube context which failed to be read: %v", err)
85+
}
86+
}
87+
}
88+
89+
return utils.AskQuestionFromPrompt("Please enter a reason/justification for the access request to create: ")
90+
}
91+
92+
// runCreateAccessRequest creates access request for the given cluster
93+
func runCreateAccessRequest(cmd *cobra.Command, args []string) error {
94+
clusterID, err := accessrequest.GetClusterID(cmd)
95+
if err != nil {
96+
return fmt.Errorf("failed to compute cluster ID: %v", err)
97+
}
98+
99+
ocmConnection, err := ocmcli.NewConnection().Build()
100+
if err != nil {
101+
return fmt.Errorf("failed to create OCM connection: %v", err)
102+
}
103+
defer ocmConnection.Close()
104+
105+
accessRequest, err := accessrequest.GetAccessRequest(ocmConnection, clusterID)
106+
107+
if err != nil {
108+
return err
109+
}
110+
111+
if accessRequest != nil {
112+
accessrequest.PrintAccessRequest(clusterID, accessRequest)
113+
114+
return fmt.Errorf("there is already an active access request for cluster '%s', eventually consider expiring it running 'ocm-backplane accessrequest expire'", clusterID)
115+
}
116+
117+
reason := options.reason
118+
if reason == "" {
119+
reason = retrieveOrPromptReason(cmd)
120+
if reason == "" {
121+
return errors.New("no reason/justification, consider using the --reason option with a non empty string")
122+
}
123+
}
124+
125+
accessRequest, err = accessrequest.CreateAccessRequest(ocmConnection, clusterID, reason, options.notificationIssueID, options.approvalDuration)
126+
127+
if err != nil {
128+
return err
129+
}
130+
131+
accessrequest.PrintAccessRequest(clusterID, accessRequest)
132+
133+
return nil
134+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package accessrequest
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/openshift/backplane-cli/pkg/accessrequest"
7+
8+
ocmcli "github.com/openshift-online/ocm-cli/pkg/ocm"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// newExpireAccessRequestCmd returns cobra command
13+
func newExpireAccessRequestCmd() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "expire",
16+
Short: "Expire the active (pending or approved) access request",
17+
Args: cobra.ExactArgs(0),
18+
SilenceUsage: true,
19+
SilenceErrors: true,
20+
RunE: runExpireAccessRequest,
21+
}
22+
23+
return cmd
24+
}
25+
26+
// runExpireAccessRequest retrieves the active access request and expire it
27+
func runExpireAccessRequest(cmd *cobra.Command, args []string) error {
28+
clusterID, err := accessrequest.GetClusterID(cmd)
29+
if err != nil {
30+
return fmt.Errorf("failed to compute cluster ID: %v", err)
31+
}
32+
33+
ocmConnection, err := ocmcli.NewConnection().Build()
34+
if err != nil {
35+
return fmt.Errorf("failed to create OCM connection: %v", err)
36+
}
37+
defer ocmConnection.Close()
38+
39+
accessRequest, err := accessrequest.GetAccessRequest(ocmConnection, clusterID)
40+
41+
if err != nil {
42+
return fmt.Errorf("failed to retrieve access request: %v", err)
43+
}
44+
45+
if accessRequest == nil {
46+
return fmt.Errorf("no pending or approved access request for cluster '%s'", clusterID)
47+
}
48+
49+
err = accessrequest.ExpireAccessRequest(ocmConnection, accessRequest)
50+
if err != nil {
51+
return err
52+
}
53+
54+
fmt.Printf("Access request '%s' has been expired\n", accessRequest.HREF())
55+
56+
return nil
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package accessrequest
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/openshift/backplane-cli/pkg/accessrequest"
7+
8+
ocmcli "github.com/openshift-online/ocm-cli/pkg/ocm"
9+
logger "github.com/sirupsen/logrus"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// newGetAccessRequestCmd returns cobra command
14+
func newGetAccessRequestCmd() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "get",
17+
Short: "Get the active (pending or approved) access request",
18+
Args: cobra.ExactArgs(0),
19+
SilenceUsage: true,
20+
SilenceErrors: true,
21+
RunE: runGetAccessRequest,
22+
}
23+
24+
return cmd
25+
}
26+
27+
// runGetAccessRequest retrieves the active access request and print it
28+
func runGetAccessRequest(cmd *cobra.Command, args []string) error {
29+
clusterID, err := accessrequest.GetClusterID(cmd)
30+
if err != nil {
31+
return fmt.Errorf("failed to compute cluster ID: %v", err)
32+
}
33+
34+
ocmConnection, err := ocmcli.NewConnection().Build()
35+
if err != nil {
36+
return fmt.Errorf("failed to create OCM connection: %v", err)
37+
}
38+
defer ocmConnection.Close()
39+
40+
accessRequest, err := accessrequest.GetAccessRequest(ocmConnection, clusterID)
41+
42+
if err != nil {
43+
return err
44+
}
45+
46+
if accessRequest == nil {
47+
logger.Warnf("no pending or approved access request for cluster '%s'", clusterID)
48+
fmt.Printf("To get denied or expired access requests, run: ocm get /api/access_transparency/v1/access_requests -p search=\"cluster_id='%s'\"\n", clusterID)
49+
} else {
50+
accessrequest.PrintAccessRequest(clusterID, accessRequest)
51+
}
52+
53+
return nil
54+
}

cmd/ocm-backplane/config/set.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func setConfig(cmd *cobra.Command, args []string) error {
5353
}
5454

5555
bpConfig.SessionDirectory = viper.GetString("session-dir")
56+
bpConfig.JiraToken = viper.GetString(config.JiraTokenViperKey)
5657
}
5758

5859
// create config directory if it doesn't exist
@@ -90,15 +91,18 @@ func setConfig(cmd *cobra.Command, args []string) error {
9091
bpConfig.SessionDirectory = args[1]
9192
case PagerDutyAPIConfigVar:
9293
bpConfig.PagerDutyAPIKey = args[1]
94+
case config.JiraTokenViperKey:
95+
bpConfig.JiraToken = args[1]
9396
default:
94-
return fmt.Errorf("supported config variables are %s, %s, %s & %s", URLConfigVar, ProxyURLConfigVar, SessionConfigVar, PagerDutyAPIConfigVar)
97+
return fmt.Errorf("supported config variables are %s, %s, %s, %s & %s", URLConfigVar, ProxyURLConfigVar, SessionConfigVar, PagerDutyAPIConfigVar, config.JiraTokenViperKey)
9598
}
9699

97100
viper.SetConfigType("json")
98101
viper.Set(URLConfigVar, bpConfig.URL)
99102
viper.Set(ProxyURLConfigVar, bpConfig.ProxyURL)
100103
viper.Set(SessionConfigVar, bpConfig.SessionDirectory)
101104
viper.Set(PagerDutyAPIConfigVar, bpConfig.PagerDutyAPIKey)
105+
viper.Set(config.JiraTokenViperKey, bpConfig.JiraToken)
102106

103107
err = viper.WriteConfigAs(configPath)
104108
if err != nil {

cmd/ocm-backplane/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
log "github.com/sirupsen/logrus"
2323
"github.com/spf13/cobra"
2424

25+
"github.com/openshift/backplane-cli/cmd/ocm-backplane/accessrequest"
2526
"github.com/openshift/backplane-cli/cmd/ocm-backplane/cloud"
2627
"github.com/openshift/backplane-cli/cmd/ocm-backplane/config"
2728
"github.com/openshift/backplane-cli/cmd/ocm-backplane/console"
@@ -64,6 +65,7 @@ func init() {
6465
globalflags.AddVerbosityFlag(rootCmd)
6566

6667
// Register sub-commands
68+
rootCmd.AddCommand(accessrequest.NewAccessRequestCmd())
6769
rootCmd.AddCommand(console.NewConsoleCmd())
6870
rootCmd.AddCommand(config.NewConfigCmd())
6971
rootCmd.AddCommand(cloud.CloudCmd)

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.21
55
require (
66
github.com/Masterminds/semver v1.5.0
77
github.com/PagerDuty/go-pagerduty v1.8.0
8+
github.com/andygrunwald/go-jira v1.16.0
89
github.com/aws/aws-sdk-go-v2 v1.30.1
910
github.com/aws/aws-sdk-go-v2/config v1.27.24
1011
github.com/aws/aws-sdk-go-v2/credentials v1.17.24
@@ -59,6 +60,7 @@ require (
5960
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
6061
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
6162
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
63+
github.com/fatih/structs v1.1.0 // indirect
6264
github.com/fsnotify/fsnotify v1.7.0 // indirect
6365
github.com/getkin/kin-openapi v0.113.0 // indirect
6466
github.com/go-errors/errors v1.4.2 // indirect
@@ -121,6 +123,7 @@ require (
121123
github.com/spf13/afero v1.11.0 // indirect
122124
github.com/spf13/cast v1.6.0 // indirect
123125
github.com/subosito/gotenv v1.6.0 // indirect
126+
github.com/trivago/tgo v1.0.7 // indirect
124127
github.com/xlab/treeprint v1.2.0 // indirect
125128
github.com/zalando/go-keyring v0.2.3 // indirect
126129
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect

0 commit comments

Comments
 (0)