Skip to content

Commit d7482fe

Browse files
OSD-24268: Determine backplane elevate reason automatically when login via pagerduty incidents (#441)
* Refactor elevate command and move kubeconfig func to kubeconfig module * refactor login command to seperate login types * OSD-22250: Added backplane elevate reason when login via pagerduty * remove unwanted config params * fixed lint * add elevate reason to config file
1 parent d82f134 commit d7482fe

File tree

8 files changed

+260
-250
lines changed

8 files changed

+260
-250
lines changed

cmd/ocm-backplane/login/login.go

+95-35
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ import (
3131
)
3232

3333
// Environment variable that for setting PS1
34-
const EnvPs1 = "KUBE_PS1_CLUSTER_FUNCTION"
34+
const (
35+
EnvPs1 = "KUBE_PS1_CLUSTER_FUNCTION"
36+
LoginTypeClusterID = "cluster-id"
37+
LoginTypeExistingKubeConfig = "kube-config"
38+
LoginTypePagerduty = "pagerduty"
39+
)
3540

3641
var (
3742
args struct {
@@ -41,6 +46,10 @@ var (
4146
defaultNamespace string
4247
}
4348

49+
// loginType derive the login type based on flags and args
50+
// set default login type as cluster-id
51+
loginType = LoginTypeClusterID
52+
4453
globalOpts = &globalflags.GlobalOptions{}
4554

4655
// LoginCmd represents the login command
@@ -64,6 +73,7 @@ var (
6473
}
6574
return nil
6675
},
76+
PreRunE: preLogin,
6777
RunE: runLogin,
6878
SilenceUsage: true,
6979
}
@@ -93,7 +103,7 @@ func init() {
93103
&args.pd,
94104
"pd",
95105
"",
96-
"Login using PagerDuty incident id or html_url.",
106+
"Login using PagerDuty incident id or pagerduty url.",
97107
)
98108
flags.StringVarP(
99109
&args.defaultNamespace,
@@ -107,6 +117,7 @@ func init() {
107117

108118
func runLogin(cmd *cobra.Command, argv []string) (err error) {
109119
var clusterKey string
120+
var elevateReason string
110121
logger.Debugf("Running Login Command ...")
111122
logger.Debugf("Checking Backplane Version")
112123
utils.CheckBackplaneVersion(cmd)
@@ -119,47 +130,29 @@ func runLogin(cmd *cobra.Command, argv []string) (err error) {
119130
}
120131
logger.Debugf("Backplane Config File Contains: %v \n", bpConfig)
121132

133+
// login to the cluster based on login type
122134
logger.Debugf("Extracting Backplane Cluster ID")
123-
// Currently go-pagerduty pkg does not include incident id validation.
124-
if args.pd != "" {
125-
if bpConfig.PagerDutyAPIKey == "" {
126-
return fmt.Errorf("please make sure the PD API Key is configured correctly in the config file")
127-
}
128-
pdClient, err := pagerduty.NewWithToken(bpConfig.PagerDutyAPIKey)
135+
switch loginType {
136+
case LoginTypePagerduty:
137+
info, err := getClusterInfoFromPagerduty(bpConfig)
129138
if err != nil {
130-
return fmt.Errorf("could not initialize the client: %w", err)
131-
}
132-
if strings.Contains(args.pd, "/incidents/") {
133-
incidentID := args.pd[strings.LastIndex(args.pd, "/")+1:]
134-
clusterKey, err = pdClient.GetClusterIDFromIncident(incidentID)
135-
if err != nil {
136-
return err
137-
}
138-
} else {
139-
clusterKey, err = pdClient.GetClusterIDFromIncident(args.pd)
140-
if err != nil {
141-
return err
142-
}
139+
return err
143140
}
144-
}
141+
clusterKey = info.ClusterID
142+
elevateReason = info.WebURL
145143

146-
// Get the cluster ID only if it hasn't been populated by PagerDuty.
147-
if len(argv) == 1 {
148-
// if explicitly one cluster key given, use it to log in.
144+
case LoginTypeClusterID:
149145
logger.Debugf("Cluster Key is given in argument")
150146
clusterKey = argv[0]
151-
logger.WithField("Search Key", clusterKey).Debugln("Finding target cluster")
152-
153-
} else if len(argv) == 0 && args.pd == "" {
154-
// if no args given, try to log into the cluster that the user is logged into
155-
logger.Debugf("Finding Clustrer Key from current cluster")
156-
clusterInfo, err := utils.DefaultClusterUtils.GetBackplaneClusterFromConfig()
147+
case LoginTypeExistingKubeConfig:
148+
clusterKey, err = getClusterIDFromExistingKubeConfig()
157149
if err != nil {
158150
return err
159151
}
160-
clusterKey = clusterInfo.ClusterID
161-
logger.Debugf("Backplane Cluster Infromation data extracted: %+v\n", clusterInfo)
152+
default:
153+
return fmt.Errorf("login type cannot be detected")
162154
}
155+
163156
logger.Debugf("Backplane Cluster Key is: %v \n", clusterKey)
164157

165158
logger.Debugln("Setting Proxy URL from global options")
@@ -353,11 +346,22 @@ func runLogin(cmd *cobra.Command, argv []string) (err error) {
353346
rc.Contexts[targetContextNickName] = targetContext
354347
rc.CurrentContext = targetContextNickName
355348

349+
// Add elevate reason to kubeconfig context
350+
if elevateReason != "" {
351+
elevationReasons, err := login.SaveElevateContextReasons(rc, elevateReason)
352+
if err != nil {
353+
return err
354+
}
355+
logger.Infof("save elevate reason: %s\n", elevationReasons)
356+
}
357+
356358
logger.Debugln("Saving new API config")
357359
// Save the config
358-
err = login.SaveKubeConfig(clusterID, rc, args.multiCluster, args.kubeConfigPath)
360+
if err = login.SaveKubeConfig(clusterID, rc, args.multiCluster, args.kubeConfigPath); err != nil {
361+
return err
362+
}
359363

360-
return err
364+
return nil
361365
}
362366

363367
// GetRestConfig returns a client-go *rest.Config which can be used to programmatically interact with the
@@ -558,3 +562,59 @@ func isValidKubernetesNamespace(namespace string) bool {
558562
pattern := `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
559563
return regexp.MustCompile(pattern).MatchString(namespace)
560564
}
565+
566+
// preLogin will execute before the command
567+
func preLogin(cmd *cobra.Command, argv []string) (err error) {
568+
569+
switch len(argv) {
570+
case 1:
571+
loginType = LoginTypeClusterID
572+
573+
case 0:
574+
if args.pd == "" {
575+
loginType = LoginTypeExistingKubeConfig
576+
} else {
577+
loginType = LoginTypePagerduty
578+
}
579+
}
580+
581+
return nil
582+
}
583+
584+
// getClusterInfoFromPagerduty returns a pagerduty.Alert from Pagerduty incident,
585+
// which contains alert info including the cluster id.
586+
func getClusterInfoFromPagerduty(bpConfig config.BackplaneConfiguration) (alert pagerduty.Alert, err error) {
587+
if bpConfig.PagerDutyAPIKey == "" {
588+
return alert, fmt.Errorf("please make sure the PD API Key is configured correctly in the config file")
589+
}
590+
pdClient, err := pagerduty.NewWithToken(bpConfig.PagerDutyAPIKey)
591+
if err != nil {
592+
return alert, fmt.Errorf("could not initialize the client: %w", err)
593+
}
594+
if strings.Contains(args.pd, "/incidents/") {
595+
incidentID := args.pd[strings.LastIndex(args.pd, "/")+1:]
596+
alert, err = pdClient.GetClusterInfoFromIncident(incidentID)
597+
if err != nil {
598+
return alert, err
599+
}
600+
} else {
601+
alert, err = pdClient.GetClusterInfoFromIncident(args.pd)
602+
if err != nil {
603+
return alert, err
604+
}
605+
}
606+
return alert, nil
607+
}
608+
609+
// getClusterIDFromExistingKubeConfig returns clusterId from kubeconfig
610+
func getClusterIDFromExistingKubeConfig() (string, error) {
611+
var clusterKey string
612+
logger.Debugf("Finding Clustrer Key from current cluster")
613+
clusterInfo, err := utils.DefaultClusterUtils.GetBackplaneClusterFromConfig()
614+
if err != nil {
615+
return "", err
616+
}
617+
clusterKey = clusterInfo.ClusterID
618+
logger.Debugf("Backplane Cluster Infromation data extracted: %+v\n", clusterInfo)
619+
return clusterKey, nil
620+
}

cmd/ocm-backplane/login/login_test.go

+13-10
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ var _ = Describe("Login command", func() {
105105
mockCluster = &cmv1.Cluster{}
106106

107107
backplaneConfiguration = config.BackplaneConfiguration{URL: backplaneAPIURI}
108+
109+
loginType = LoginTypeClusterID
108110
})
109111

110112
AfterEach(func() {
@@ -184,6 +186,7 @@ var _ = Describe("Login command", func() {
184186
err := utils.CreateTempKubeConfig(nil)
185187
Expect(err).To(BeNil())
186188
globalOpts.ProxyURL = "https://squid.myproxy.com"
189+
os.Setenv("HTTPS_PROXY", "https://squid.myproxy.com")
187190
mockOcmInterface.EXPECT().GetOCMEnvironment().Return(ocmEnv, nil).AnyTimes()
188191
mockClientUtil.EXPECT().SetClientProxyURL(globalOpts.ProxyURL).Return(nil)
189192
mockOcmInterface.EXPECT().GetTargetCluster(testClusterID).Return(trueClusterID, testClusterID, nil)
@@ -345,6 +348,7 @@ var _ = Describe("Login command", func() {
345348
})
346349

347350
It("should login to current cluster if cluster id not provided", func() {
351+
loginType = LoginTypeExistingKubeConfig
348352
err := utils.CreateTempKubeConfig(nil)
349353
Expect(err).To(BeNil())
350354
globalOpts.ProxyURL = "https://squid.myproxy.com"
@@ -431,6 +435,7 @@ var _ = Describe("Login command", func() {
431435
})
432436

433437
It("should fail to create PD API client and return HTTP status code 401 when unauthorized", func() {
438+
loginType = LoginTypePagerduty
434439
args.pd = truePagerDutyIncidentID
435440

436441
err := utils.CreateTempKubeConfig(nil)
@@ -466,6 +471,7 @@ var _ = Describe("Login command", func() {
466471
})
467472

468473
It("should return error when trying to login via PD but the PD API Key is not configured", func() {
474+
loginType = LoginTypePagerduty
469475
args.pd = truePagerDutyIncidentID
470476

471477
err := utils.CreateTempKubeConfig(nil)
@@ -480,11 +486,9 @@ var _ = Describe("Login command", func() {
480486
Expect(err).To(BeNil())
481487

482488
testData := config.BackplaneConfiguration{
483-
URL: backplaneAPIURI,
484-
ProxyURL: new(string),
485-
SessionDirectory: "",
486-
AssumeInitialArn: "",
487-
PagerDutyAPIKey: falsePagerDutyAPITkn,
489+
URL: backplaneAPIURI,
490+
ProxyURL: new(string),
491+
PagerDutyAPIKey: falsePagerDutyAPITkn,
488492
}
489493

490494
// Marshal the testData into JSON format and write it to tempFile.
@@ -503,6 +507,7 @@ var _ = Describe("Login command", func() {
503507
})
504508

505509
It("should fail to find a non existent PD Incident and return HTTP status code 404 when the requested resource is not found", func() {
510+
loginType = LoginTypePagerduty
506511
args.pd = falsePagerDutyIncidentID
507512

508513
err := utils.CreateTempKubeConfig(nil)
@@ -517,11 +522,9 @@ var _ = Describe("Login command", func() {
517522
Expect(err).To(BeNil())
518523

519524
testData := config.BackplaneConfiguration{
520-
URL: backplaneAPIURI,
521-
ProxyURL: new(string),
522-
SessionDirectory: "",
523-
AssumeInitialArn: "",
524-
PagerDutyAPIKey: truePagerDutyAPITkn,
525+
URL: backplaneAPIURI,
526+
ProxyURL: new(string),
527+
PagerDutyAPIKey: truePagerDutyAPITkn,
525528
}
526529

527530
// Marshal the testData into JSON format and write it to tempFile.

pkg/elevate/elevate.go

+3-30
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package elevate
22

33
import (
4-
"errors"
54
"fmt"
65
"os"
76
"os/exec"
87

98
logger "github.com/sirupsen/logrus"
10-
"k8s.io/client-go/tools/clientcmd/api"
119

10+
"github.com/openshift/backplane-cli/pkg/login"
1211
"github.com/openshift/backplane-cli/pkg/utils"
1312
)
1413

@@ -19,32 +18,6 @@ var (
1918
WriteKubeconfigToFile = utils.CreateTempKubeConfig
2019
)
2120

22-
func AddElevationReasonToRawKubeconfig(config api.Config, elevationReason string) error {
23-
return AddElevationReasonsToRawKubeconfig(config, []string{elevationReason})
24-
}
25-
26-
func AddElevationReasonsToRawKubeconfig(config api.Config, elevationReasons []string) error {
27-
logger.Debugln("Adding reason for backplane-cluster-admin elevation")
28-
if config.Contexts[config.CurrentContext] == nil {
29-
return errors.New("no current kubeconfig context")
30-
}
31-
32-
currentCtxUsername := config.Contexts[config.CurrentContext].AuthInfo
33-
34-
if config.AuthInfos[currentCtxUsername] == nil {
35-
return errors.New("no current user information")
36-
}
37-
38-
if config.AuthInfos[currentCtxUsername].ImpersonateUserExtra == nil {
39-
config.AuthInfos[currentCtxUsername].ImpersonateUserExtra = make(map[string][]string)
40-
}
41-
42-
config.AuthInfos[currentCtxUsername].ImpersonateUserExtra["reason"] = elevationReasons
43-
config.AuthInfos[currentCtxUsername].Impersonate = "backplane-cluster-admin"
44-
45-
return nil
46-
}
47-
4821
func RunElevate(argv []string) error {
4922
logger.Debugln("Finding target cluster from kubeconfig")
5023
config, err := ReadKubeConfigRaw()
@@ -59,7 +32,7 @@ func RunElevate(argv []string) error {
5932
} else {
6033
elevateReason = argv[0]
6134
}
62-
elevationReasons, err := ComputeElevateContextAndStoreToKubeConfigFileAndGetReasons(config, elevateReason)
35+
elevationReasons, err := login.SaveElevateContextReasons(config, elevateReason)
6336
if err != nil {
6437
return err
6538
}
@@ -70,7 +43,7 @@ func RunElevate(argv []string) error {
7043
}
7144

7245
logger.Debug("Adding impersonation RBAC allow permissions to kubeconfig")
73-
err = AddElevationReasonsToRawKubeconfig(config, elevationReasons)
46+
err = login.AddElevationReasonsToRawKubeconfig(config, elevationReasons)
7447
if err != nil {
7548
return err
7649
}

0 commit comments

Comments
 (0)