Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login through PagerDuty incident link or ID #368

Merged
merged 1 commit into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ Logging into multiple clusters via different terminal instances.
$ export KUBECONFIG= <cluster-id-2-kube-config-path>
```

### Login through PagerDuty incident link or ID

- [Generate a User Token REST API Key](https://support.pagerduty.com/docs/api-access-keys#generate-a-user-token-rest-api-key) and save it into backplane config file.
```
$ ocm backplane config set pd-key <api-key>
```
Replace `<api-key>` with the actual User Token REST API Key obtained from PagerDuty.

- To log in using the PagerDuty incident link, use the following command:
```
$ ocm backplane login --pd https://{your-pd-domain}.pagerduty.com/incidents/<incident-id>
```
Replace `<incident-id>` with the specific incident ID you want to access.

- Alternatively, if you have the incident ID, you can use the following command:
```
$ ocm backplane login --pd <incident-id>
```
Replace `<incident-id>` with the specific incident ID you want to access.

## Console

- Login to the target cluster via backplane as the above.
Expand Down
8 changes: 5 additions & 3 deletions cmd/ocm-backplane/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
)

const (
ProxyURLConfigVar = "proxy-url"
URLConfigVar = "url"
SessionConfigVar = "session-dir"
ProxyURLConfigVar = "proxy-url"
URLConfigVar = "url"
SessionConfigVar = "session-dir"
PagerDutyAPIConfigVar = "pd-key"
)

func NewConfigCmd() *cobra.Command {
Expand All @@ -21,6 +22,7 @@ The following variables are supported:
url Backplane API URL
proxy-url Squid proxy URL
session-dir Backplane CLI session directory
pd-key PagerDuty API User Key
`,
SilenceUsage: true,
}
Expand Down
5 changes: 4 additions & 1 deletion cmd/ocm-backplane/config/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ func getConfig(cmd *cobra.Command, args []string) error {
fmt.Printf("%s: %s\n", ProxyURLConfigVar, proxyURL)
case SessionConfigVar:
fmt.Printf("%s: %s\n", SessionConfigVar, config.SessionDirectory)
case PagerDutyAPIConfigVar:
fmt.Printf("%s: %s\n", PagerDutyAPIConfigVar, config.PagerDutyAPIKey)
case "all":
fmt.Printf("%s: %s\n", URLConfigVar, config.URL)
fmt.Printf("%s: %s\n", ProxyURLConfigVar, proxyURL)
fmt.Printf("%s: %s\n", SessionConfigVar, config.SessionDirectory)
fmt.Printf("%s: %s\n", PagerDutyAPIConfigVar, config.PagerDutyAPIKey)
default:
return fmt.Errorf("supported config variables are %s, %s & %s", URLConfigVar, ProxyURLConfigVar, SessionConfigVar)
return fmt.Errorf("supported config variables are %s, %s, %s & %s", URLConfigVar, ProxyURLConfigVar, SessionConfigVar, PagerDutyAPIConfigVar)
}

return nil
Expand Down
10 changes: 9 additions & 1 deletion cmd/ocm-backplane/config/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ func setConfig(cmd *cobra.Command, args []string) error {
bpConfig.ProxyURL = &proxyURL
}

pagerDutyAPIKey := viper.GetString("pd-key")
if pagerDutyAPIKey != "" {
bpConfig.PagerDutyAPIKey = pagerDutyAPIKey
}

bpConfig.SessionDirectory = viper.GetString("session-dir")
}

Expand Down Expand Up @@ -83,14 +88,17 @@ func setConfig(cmd *cobra.Command, args []string) error {
bpConfig.ProxyURL = &args[1]
case SessionConfigVar:
bpConfig.SessionDirectory = args[1]
case PagerDutyAPIConfigVar:
bpConfig.PagerDutyAPIKey = args[1]
default:
return fmt.Errorf("supported config variables are %s, %s & %s", URLConfigVar, ProxyURLConfigVar, SessionConfigVar)
return fmt.Errorf("supported config variables are %s, %s, %s & %s", URLConfigVar, ProxyURLConfigVar, SessionConfigVar, PagerDutyAPIConfigVar)
}

viper.SetConfigType("json")
viper.Set(URLConfigVar, bpConfig.URL)
viper.Set(ProxyURLConfigVar, bpConfig.ProxyURL)
viper.Set(SessionConfigVar, bpConfig.SessionDirectory)
viper.Set(PagerDutyAPIConfigVar, bpConfig.PagerDutyAPIKey)

err = viper.WriteConfigAs(configPath)
if err != nil {
Expand Down
61 changes: 50 additions & 11 deletions cmd/ocm-backplane/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/openshift/backplane-cli/pkg/cli/globalflags"
"github.com/openshift/backplane-cli/pkg/login"
"github.com/openshift/backplane-cli/pkg/ocm"
"github.com/openshift/backplane-cli/pkg/pagerduty"
"github.com/openshift/backplane-cli/pkg/utils"
)

Expand All @@ -34,6 +35,7 @@ var (
args struct {
multiCluster bool
kubeConfigPath string
pd string
}

globalOpts = &globalflags.GlobalOptions{}
Expand All @@ -46,8 +48,19 @@ var (
using OCM token. The backplane api will return a proxy url for
target cluster. The url will be written to kubeconfig, so we can
run oc command later to operate the target cluster.`,
Example: " backplane login <id>\n backplane login %test%\n backplane login <external_id>",
Args: cobra.ExactArgs(1),
Example: " backplane login <id>\n backplane login %test%\n backplane login <external_id>\n backplane login --pd <incident-id>",
Args: func(cmd *cobra.Command, args []string) error {
if cmd.Flags().Lookup("pd").Changed {
if err := cobra.ExactArgs(0)(cmd, args); err != nil {
return err
}
} else {
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
return err
}
}
return nil
},
RunE: runLogin,
SilenceUsage: true,
}
Expand All @@ -73,6 +86,12 @@ func init() {
"Save kube configuration in the specific path when login to multi clusters.",
)

flags.StringVar(
&args.pd,
"pd",
"",
"Login using PagerDuty incident id or html_url.",
)
}

func runLogin(cmd *cobra.Command, argv []string) (err error) {
Expand All @@ -81,8 +100,36 @@ func runLogin(cmd *cobra.Command, argv []string) (err error) {
logger.Debugf("Checking Backplane Version")
utils.CheckBackplaneVersion(cmd)

logger.Debugf("Extracting Backplane configuration")
// Get Backplane configuration
bpConfig, err := config.GetBackplaneConfiguration()
if err != nil {
return err
}
logger.Debugf("Backplane Config File Contains is: %v \n", bpConfig)

logger.Debugf("Extracting Backplane Cluster ID")
// Get The cluster ID
// Currently go-pagerduty pkg does not include incident id validation.
if args.pd != "" {
pdClient, err := pagerduty.NewWithToken(bpConfig.PagerDutyAPIKey)
if err != nil {
return fmt.Errorf("could not initialize the client: %w", err)
}
if strings.Contains(args.pd, "/incidents/") {
incidentID := args.pd[strings.LastIndex(args.pd, "/")+1:]
clusterKey, err = pdClient.GetClusterID(incidentID)
if err != nil {
return err
}
} else {
clusterKey, err = pdClient.GetClusterID(args.pd)
if err != nil {
return err
}
}
}

// Get the cluster ID only if it hasn't been populated by PagerDuty.
if len(argv) == 1 {
// if explicitly one cluster key given, use it to log in.
logger.Debugf("Cluster Key is given in argument")
Expand All @@ -101,14 +148,6 @@ func runLogin(cmd *cobra.Command, argv []string) (err error) {
}
logger.Debugf("Backplane Cluster Key is: %v \n", clusterKey)

logger.Debugf("Extracting Backplane configuration")
// Get Backplane configuration
bpConfig, err := config.GetBackplaneConfiguration()
if err != nil {
return err
}
logger.Debugf("Backplane Config File Contains is: %v \n", bpConfig)

logger.Debugln("Setting Proxy URL from global options")
// Set proxy url to http client
proxyURL := globalOpts.ProxyURL
Expand Down
126 changes: 105 additions & 21 deletions cmd/ocm-backplane/login/login_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package login

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/golang/mock/gomock"
Expand Down Expand Up @@ -34,24 +36,29 @@ func MakeIoReader(s string) io.ReadCloser {
var _ = Describe("Login command", func() {

var (
mockCtrl *gomock.Controller
mockClient *mocks.MockClientInterface
mockClientWithResp *mocks.MockClientWithResponsesInterface
mockOcmInterface *ocmMock.MockOCMInterface
mockClientUtil *backplaneapiMock.MockClientUtils

testClusterID string
testToken string
trueClusterID string
managingClusterID string
backplaneAPIURI string
serviceClusterID string
serviceClusterName string
fakeResp *http.Response
ocmEnv *cmv1.Environment
kubeConfigPath string
mockCluster *cmv1.Cluster
backplaneConfiguration config.BackplaneConfiguration
mockCtrl *gomock.Controller
mockClient *mocks.MockClientInterface
mockClientWithResp *mocks.MockClientWithResponsesInterface
mockOcmInterface *ocmMock.MockOCMInterface
mockClientUtil *backplaneapiMock.MockClientUtils

testClusterID string
testToken string
trueClusterID string
managingClusterID string
backplaneAPIURI string
serviceClusterID string
serviceClusterName string
fakeResp *http.Response
ocmEnv *cmv1.Environment
kubeConfigPath string
mockCluster *cmv1.Cluster
backplaneConfiguration config.BackplaneConfiguration
falsePagerDutyAPITkn string
truePagerDutyAPITkn string
falsePagerDutyIncidentID string
truePagerDutyIncidentID string
bpConfigPath string
)

BeforeEach(func() {
Expand All @@ -73,6 +80,11 @@ var _ = Describe("Login command", func() {
serviceClusterName = "hs-sc-654321"
backplaneAPIURI = "https://shard.apps"
kubeConfigPath = "filepath"
falsePagerDutyAPITkn = "token123"
// nolint:gosec truePagerDutyAPIToken refers to the Test API Token provided by https://developer.pagerduty.com/api-reference
truePagerDutyAPITkn = "y_NbAkKc66ryYTWUXYEu"
falsePagerDutyIncidentID = "incident123"
truePagerDutyIncidentID = "Q0ZNH7TDQBOO54"

mockClientWithResp.EXPECT().LoginClusterWithResponse(gomock.Any(), gomock.Any()).Return(nil, nil).Times(0)

Expand All @@ -92,7 +104,7 @@ var _ = Describe("Login command", func() {
ocmEnv, _ = cmv1.NewEnvironment().BackplaneURL("https://dummy.api").Build()

mockCluster = &cmv1.Cluster{}

backplaneConfiguration = config.BackplaneConfiguration{URL: backplaneAPIURI}
})

Expand All @@ -102,6 +114,8 @@ var _ = Describe("Login command", func() {
globalOpts.BackplaneURL = ""
globalOpts.ProxyURL = ""
os.Setenv("HTTPS_PROXY", "")
os.Unsetenv("BACKPLANE_CONFIG")
os.Remove(bpConfigPath)
mockCtrl.Finish()
utils.RemoveTempKubeConfig()
})
Expand Down Expand Up @@ -393,11 +407,81 @@ var _ = Describe("Login command", func() {

})

It("should fail to create PD API client and return HTTP status code 401 when unauthorized", func() {
args.pd = truePagerDutyIncidentID

err := utils.CreateTempKubeConfig(nil)
Expect(err).To(BeNil())

mockOcmInterface.EXPECT().GetOCMEnvironment().Return(ocmEnv, nil).AnyTimes()

// Create a temporary JSON configuration file in the temp directory for testing purposes.
tempDir := os.TempDir()
bpConfigPath = filepath.Join(tempDir, "mock.json")
tempFile, err := os.Create(bpConfigPath)
Expect(err).To(BeNil())

testData := config.BackplaneConfiguration{
URL: backplaneAPIURI,
ProxyURL: new(string),
SessionDirectory: "",
AssumeInitialArn: "",
PagerDutyAPIKey: falsePagerDutyAPITkn,
}

// Marshal the testData into JSON format and write it to tempFile.
jsonData, err := json.Marshal(testData)
Expect(err).To(BeNil())
_, err = tempFile.Write(jsonData)
Expect(err).To(BeNil())

os.Setenv("BACKPLANE_CONFIG", bpConfigPath)

err = runLogin(nil, nil)

Expect(err.Error()).Should(ContainSubstring("status code 401"))
})

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

err := utils.CreateTempKubeConfig(nil)
Expect(err).To(BeNil())

mockOcmInterface.EXPECT().GetOCMEnvironment().Return(ocmEnv, nil).AnyTimes()

// Create a temporary JSON configuration file in the temp directory for testing purposes.
tempDir := os.TempDir()
bpConfigPath = filepath.Join(tempDir, "mock.json")
tempFile, err := os.Create(bpConfigPath)
Expect(err).To(BeNil())

testData := config.BackplaneConfiguration{
URL: backplaneAPIURI,
ProxyURL: new(string),
SessionDirectory: "",
AssumeInitialArn: "",
PagerDutyAPIKey: truePagerDutyAPITkn,
}

// Marshal the testData into JSON format and write it to tempFile.
jsonData, err := json.Marshal(testData)
Expect(err).To(BeNil())
_, err = tempFile.Write(jsonData)
Expect(err).To(BeNil())

os.Setenv("BACKPLANE_CONFIG", bpConfigPath)

err = runLogin(nil, nil)

Expect(err.Error()).Should(ContainSubstring("status code 404"))
})

})

Context("check GetRestConfigAsUser", func() {

It("check config creation with username and without elevationReasons",func () {
It("check config creation with username and without elevationReasons", func() {
mockOcmInterface.EXPECT().GetClusterInfoByID(testClusterID).Return(mockCluster, nil)
mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil)
mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil)
Expand All @@ -412,7 +496,7 @@ var _ = Describe("Login command", func() {

})

It("check config creation with username and elevationReasons",func () {
It("check config creation with username and elevationReasons", func() {
mockOcmInterface.EXPECT().GetClusterInfoByID(testClusterID).Return(mockCluster, nil)
mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil)
mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil)
Expand Down
Loading