diff --git a/Makefile b/Makefile index 588f2aac..c0ef1bdb 100644 --- a/Makefile +++ b/Makefile @@ -115,7 +115,7 @@ mock-gen: mockgen -destination=./pkg/cli/session/mocks/sessionMock.go -package=mocks github.com/openshift/backplane-cli/pkg/cli/session BackplaneSessionInterface mockgen -destination=./pkg/utils/mocks/shellCheckerMock.go -package=mocks github.com/openshift/backplane-cli/pkg/utils ShellCheckerInterface mockgen -destination=./pkg/pagerduty/mocks/clientMock.go -package=mocks github.com/openshift/backplane-cli/pkg/pagerduty PagerDutyClient - mockgen -destination=./pkg/utils/mocks/jiraMock.go -package=mocks github.com/openshift/backplane-cli/pkg/utils IssueServiceInterface + mockgen -destination=./pkg/jira/mocks/jiraMock.go -package=mocks github.com/openshift/backplane-cli/pkg/jira IssueServiceInterface .PHONY: build-image build-image: diff --git a/cmd/ocm-backplane/login/login.go b/cmd/ocm-backplane/login/login.go index 6da0cd10..c42c1cf7 100644 --- a/cmd/ocm-backplane/login/login.go +++ b/cmd/ocm-backplane/login/login.go @@ -24,6 +24,7 @@ import ( "github.com/openshift/backplane-cli/pkg/backplaneapi" "github.com/openshift/backplane-cli/pkg/cli/config" "github.com/openshift/backplane-cli/pkg/cli/globalflags" + "github.com/openshift/backplane-cli/pkg/jira" "github.com/openshift/backplane-cli/pkg/login" "github.com/openshift/backplane-cli/pkg/ocm" "github.com/openshift/backplane-cli/pkg/pagerduty" @@ -36,6 +37,7 @@ const ( LoginTypeClusterID = "cluster-id" LoginTypeExistingKubeConfig = "kube-config" LoginTypePagerduty = "pagerduty" + LoginTypeJira = "jira" ) var ( @@ -44,6 +46,7 @@ var ( kubeConfigPath string pd string defaultNamespace string + ohss string } // loginType derive the login type based on flags and args @@ -62,7 +65,7 @@ var ( run oc command later to operate the target cluster.`, Example: " backplane login \n backplane login %test%\n backplane login \n backplane login --pd ", Args: func(cmd *cobra.Command, args []string) error { - if cmd.Flags().Lookup("pd").Changed { + if cmd.Flags().Lookup("pd").Changed || cmd.Flags().Lookup("ohss").Changed { if err := cobra.ExactArgs(0)(cmd, args); err != nil { return err } @@ -77,6 +80,9 @@ var ( RunE: runLogin, SilenceUsage: true, } + + //ohss service + ohssService *jira.OHSSService ) func init() { @@ -112,6 +118,12 @@ func init() { "default", "The default namespace for a user to execute commands in", ) + flags.StringVar( + &args.ohss, + "ohss", + "", + "Login using JIRA Id", + ) } @@ -140,7 +152,16 @@ func runLogin(cmd *cobra.Command, argv []string) (err error) { } clusterKey = info.ClusterID elevateReason = info.WebURL - + case LoginTypeJira: + ohssIssue, err := getClusterInfoFromJira() + if err != nil { + return err + } + if ohssIssue.ClusterID == "" { + return fmt.Errorf("clusterID cannot be detected for JIRA issue:%s", args.ohss) + } + clusterKey = ohssIssue.ClusterID + elevateReason = ohssIssue.WebURL case LoginTypeClusterID: logger.Debugf("Cluster Key is given in argument") clusterKey = argv[0] @@ -571,9 +592,11 @@ func preLogin(cmd *cobra.Command, argv []string) (err error) { loginType = LoginTypeClusterID case 0: - if args.pd == "" { + if args.pd == "" && args.ohss == "" { loginType = LoginTypeExistingKubeConfig - } else { + } else if args.ohss != "" { + loginType = LoginTypeJira + } else if args.pd != "" { loginType = LoginTypePagerduty } } @@ -606,6 +629,20 @@ func getClusterInfoFromPagerduty(bpConfig config.BackplaneConfiguration) (alert return alert, nil } +// getClusterInfoFromJira returns a cluster info OHSS card +func getClusterInfoFromJira() (ohss jira.OHSSIssue, err error) { + if ohssService == nil { + ohssService = jira.NewOHSSService(jira.DefaultIssueService) + } + + ohss, err = ohssService.GetIssue(args.ohss) + if err != nil { + return ohss, err + } + + return ohss, nil +} + // getClusterIDFromExistingKubeConfig returns clusterId from kubeconfig func getClusterIDFromExistingKubeConfig() (string, error) { var clusterKey string diff --git a/cmd/ocm-backplane/login/login_test.go b/cmd/ocm-backplane/login/login_test.go index 4fe5a999..9c0fa3cb 100644 --- a/cmd/ocm-backplane/login/login_test.go +++ b/cmd/ocm-backplane/login/login_test.go @@ -14,13 +14,17 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + "github.com/trivago/tgo/tcontainer" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" + "github.com/andygrunwald/go-jira" "github.com/openshift/backplane-cli/pkg/backplaneapi" backplaneapiMock "github.com/openshift/backplane-cli/pkg/backplaneapi/mocks" "github.com/openshift/backplane-cli/pkg/cli/config" "github.com/openshift/backplane-cli/pkg/client/mocks" + jiraClient "github.com/openshift/backplane-cli/pkg/jira" + jiraMock "github.com/openshift/backplane-cli/pkg/jira/mocks" "github.com/openshift/backplane-cli/pkg/login" "github.com/openshift/backplane-cli/pkg/ocm" ocmMock "github.com/openshift/backplane-cli/pkg/ocm/mocks" @@ -40,6 +44,7 @@ var _ = Describe("Login command", func() { mockClientWithResp *mocks.MockClientWithResponsesInterface mockOcmInterface *ocmMock.MockOCMInterface mockClientUtil *backplaneapiMock.MockClientUtils + mockIssueService *jiraMock.MockIssueServiceInterface testClusterID string testToken string @@ -576,4 +581,62 @@ var _ = Describe("Login command", func() { }) }) + + Context("check JIRA OHSS login", func() { + var ( + testOHSSID string + testIssue jira.Issue + issueFields *jira.IssueFields + ) + BeforeEach(func() { + mockIssueService = jiraMock.NewMockIssueServiceInterface(mockCtrl) + ohssService = jiraClient.NewOHSSService(mockIssueService) + testOHSSID = "OHSS-1000" + }) + + It("should login to ohss card cluster", func() { + + loginType = LoginTypeJira + args.ohss = testOHSSID + err := utils.CreateTempKubeConfig(nil) + args.kubeConfigPath = "" + Expect(err).To(BeNil()) + issueFields = &jira.IssueFields{ + Project: jira.Project{Key: jiraClient.JiraOHSSProjectKey}, + Unknowns: tcontainer.MarshalMap{jiraClient.CustomFieldClusterID: testClusterID}, + } + testIssue = jira.Issue{ID: testOHSSID, Fields: issueFields} + globalOpts.ProxyURL = "https://squid.myproxy.com" + mockIssueService.EXPECT().Get(testOHSSID, nil).Return(&testIssue, nil, nil).Times(1) + mockOcmInterface.EXPECT().GetOCMEnvironment().Return(ocmEnv, nil).AnyTimes() + mockClientUtil.EXPECT().SetClientProxyURL(globalOpts.ProxyURL).Return(nil) + mockOcmInterface.EXPECT().GetTargetCluster(testClusterID).Return(testClusterID, testClusterID, nil) + mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(testClusterID)).Return(false, nil).AnyTimes() + mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) + mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID)).Return(fakeResp, nil) + + err = runLogin(nil, nil) + + Expect(err).To(BeNil()) + }) + + It("should failed missing cluster id ohss cards", func() { + + loginType = LoginTypeJira + args.ohss = testOHSSID + + issueFields = &jira.IssueFields{ + Project: jira.Project{Key: jiraClient.JiraOHSSProjectKey}, + } + testIssue = jira.Issue{ID: testOHSSID, Fields: issueFields} + mockIssueService.EXPECT().Get(testOHSSID, nil).Return(&testIssue, nil, nil).Times(1) + mockOcmInterface.EXPECT().GetOCMEnvironment().Return(ocmEnv, nil).AnyTimes() + + err := runLogin(nil, nil) + + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal("clusterID cannot be detected for JIRA issue:OHSS-1000")) + }) + }) }) diff --git a/pkg/accessrequest/accessRequest.go b/pkg/accessrequest/accessRequest.go index abc8dfe0..aac6ff5c 100644 --- a/pkg/accessrequest/accessRequest.go +++ b/pkg/accessrequest/accessRequest.go @@ -9,6 +9,7 @@ import ( ocmsdk "github.com/openshift-online/ocm-sdk-go" acctrspv1 "github.com/openshift-online/ocm-sdk-go/accesstransparency/v1" "github.com/openshift/backplane-cli/pkg/cli/config" + jiraClient "github.com/openshift/backplane-cli/pkg/jira" "github.com/openshift/backplane-cli/pkg/ocm" "github.com/openshift/backplane-cli/pkg/utils" logger "github.com/sirupsen/logrus" @@ -114,7 +115,7 @@ func verifyAndPossiblyRetrieveIssue(bpConfig *config.BackplaneConfiguration, isP return nil, nil } - issue, _, err := utils.DefaultIssueService.Get(issueID, nil) + issue, _, err := jiraClient.DefaultIssueService.Get(issueID, nil) if err != nil { return nil, err } @@ -145,7 +146,7 @@ func createNotificationIssue(bpConfig *config.BackplaneConfiguration, isProd boo }, } - issue, _, err := utils.DefaultIssueService.Create(issue) + issue, _, err := jiraClient.DefaultIssueService.Create(issue) if err != nil { return nil, err } @@ -164,7 +165,7 @@ func getProjectFromIssueID(issueID string) string { } func transitionIssue(issueID, newTransitionName string) { - possibleTransitions, _, err := utils.DefaultIssueService.GetTransitions(issueID) + possibleTransitions, _, err := jiraClient.DefaultIssueService.GetTransitions(issueID) if err != nil { logger.Warnf("won't transition the '%s' JIRA issue to '%s' as it was not possible to retrieve the possible transitions for the issue: %v", issueID, newTransitionName, err) } else { @@ -180,7 +181,7 @@ func transitionIssue(issueID, newTransitionName string) { if transitionID == "" { logger.Warnf("won't transition the '%s' JIRA issue to '%s' as there is no transition named that way", issueID, newTransitionName) } else { - _, err := utils.DefaultIssueService.DoTransition(issueID, transitionID) + _, err := jiraClient.DefaultIssueService.DoTransition(issueID, transitionID) if err != nil { logger.Warnf("failed to transition the '%s' JIRA issue to '%s': %v", issueID, newTransitionName, err) @@ -196,7 +197,7 @@ func updateNotificationIssueDescription(issue *jira.Issue, onApprovalTransitionN onApprovalTransitionName, accessRequest.HREF()), } - _, _, err := utils.DefaultIssueService.Update(issue) + _, _, err := jiraClient.DefaultIssueService.Update(issue) if err != nil { logger.Warnf("failed to update the description of the '%s' JIRA issue: %v", issue.Key, err) } diff --git a/pkg/accessrequest/accessRequest_test.go b/pkg/accessrequest/accessRequest_test.go index e4e99937..e787fc10 100644 --- a/pkg/accessrequest/accessRequest_test.go +++ b/pkg/accessrequest/accessRequest_test.go @@ -17,10 +17,10 @@ import ( cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" "github.com/openshift/backplane-cli/pkg/backplaneapi" backplaneapiMock "github.com/openshift/backplane-cli/pkg/backplaneapi/mocks" + jiraClient "github.com/openshift/backplane-cli/pkg/jira" + jiraMocks "github.com/openshift/backplane-cli/pkg/jira/mocks" "github.com/openshift/backplane-cli/pkg/ocm" ocmMock "github.com/openshift/backplane-cli/pkg/ocm/mocks" - "github.com/openshift/backplane-cli/pkg/utils" - utilsMocks "github.com/openshift/backplane-cli/pkg/utils/mocks" ) const testDesc = "accessrequest package" @@ -57,7 +57,7 @@ var _ = Describe(testDesc, func() { mockCtrl *gomock.Controller mockClientUtil *backplaneapiMock.MockClientUtils mockOcmInterface *ocmMock.MockOCMInterface - mockIssueService *utilsMocks.MockIssueServiceInterface + mockIssueService *jiraMocks.MockIssueServiceInterface clusterID string ocmEnv *cmv1.Environment @@ -81,8 +81,8 @@ var _ = Describe(testDesc, func() { mockOcmInterface = ocmMock.NewMockOCMInterface(mockCtrl) ocm.DefaultOCMInterface = mockOcmInterface - mockIssueService = utilsMocks.NewMockIssueServiceInterface(mockCtrl) - utils.DefaultIssueService = mockIssueService + mockIssueService = jiraMocks.NewMockIssueServiceInterface(mockCtrl) + jiraClient.DefaultIssueService = mockIssueService clusterID = "cluster-12345678" diff --git a/pkg/utils/jira.go b/pkg/jira/issueService.go similarity index 99% rename from pkg/utils/jira.go rename to pkg/jira/issueService.go index 0ffa84d9..901935f4 100644 --- a/pkg/utils/jira.go +++ b/pkg/jira/issueService.go @@ -1,4 +1,4 @@ -package utils +package jira import ( "errors" diff --git a/pkg/jira/jira_suite_test.go b/pkg/jira/jira_suite_test.go new file mode 100644 index 00000000..45853149 --- /dev/null +++ b/pkg/jira/jira_suite_test.go @@ -0,0 +1,13 @@ +package jira_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestJira(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Jira Service Suite") +} diff --git a/pkg/utils/mocks/jiraMock.go b/pkg/jira/mocks/jiraMock.go similarity index 97% rename from pkg/utils/mocks/jiraMock.go rename to pkg/jira/mocks/jiraMock.go index c2a975a3..03c52882 100644 --- a/pkg/utils/mocks/jiraMock.go +++ b/pkg/jira/mocks/jiraMock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/openshift/backplane-cli/pkg/utils (interfaces: IssueServiceInterface) +// Source: github.com/openshift/backplane-cli/pkg/jira (interfaces: IssueServiceInterface) // Package mocks is a generated GoMock package. package mocks diff --git a/pkg/jira/ohssService.go b/pkg/jira/ohssService.go new file mode 100644 index 00000000..be76ed4d --- /dev/null +++ b/pkg/jira/ohssService.go @@ -0,0 +1,80 @@ +package jira + +import ( + "fmt" + "strings" + + "github.com/andygrunwald/go-jira" +) + +const ( + JiraOHSSProjectKey = "OHSS" + CustomFieldClusterID = "customfield_12316349" +) + +type OHSSIssue struct { + ID string + Key string + Title string + ProjectKey string + WebURL string + ClusterID string +} + +type OHSSService struct { + issueService IssueServiceInterface +} + +func NewOHSSService(client IssueServiceInterface) *OHSSService { + return &OHSSService{ + issueService: client, + } +} + +// GetIssue returns matching issue from OHSS project +func (j *OHSSService) GetIssue(issueID string) (ohssIssue OHSSIssue, err error) { + + if issueID == "" { + return ohssIssue, fmt.Errorf("empty issue Id") + } + issue, _, err := j.issueService.Get(issueID, nil) + if err != nil { + return ohssIssue, err + } + if issue == nil { + return ohssIssue, fmt.Errorf("no matching issue for issueID:%s", issueID) + } + + if issue.Fields != nil { + if issue.Fields.Project.Key != JiraOHSSProjectKey { + return ohssIssue, fmt.Errorf("issue %s is not belongs to OHSS project", issueID) + } + } + formatIssue, err := j.formatIssue(*issue) + if err != nil { + return ohssIssue, err + } + return formatIssue, nil + +} + +// formatIssue format the JIRA issue to OHSS Issue +func (j *OHSSService) formatIssue(issue jira.Issue) (formatIssue OHSSIssue, err error) { + + formatIssue.ID = issue.ID + formatIssue.Key = issue.Key + if issue.Fields != nil { + clusterID, clusterIDExist := issue.Fields.Unknowns[CustomFieldClusterID] + if clusterIDExist { + formatIssue.ClusterID = fmt.Sprintf("%s", clusterID) + } + formatIssue.ProjectKey = issue.Fields.Project.Key + formatIssue.Title = issue.Fields.Summary + } + if issue.Self != "" { + selfSlice := strings.SplitAfter(issue.Self, ".com") + formatIssue.WebURL = fmt.Sprintf("%s/browse/%s", selfSlice[0], issue.Key) + } + + return formatIssue, nil +} diff --git a/pkg/jira/ohssService_test.go b/pkg/jira/ohssService_test.go new file mode 100644 index 00000000..8f3be5d1 --- /dev/null +++ b/pkg/jira/ohssService_test.go @@ -0,0 +1,66 @@ +package jira + +import ( + "github.com/andygrunwald/go-jira" + "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + jiraMock "github.com/openshift/backplane-cli/pkg/jira/mocks" +) + +var _ = Describe("Jira", func() { + var ( + mockCtrl *gomock.Controller + mockIssueService *jiraMock.MockIssueServiceInterface + ohssService *OHSSService + testOHSSID string + testIssue jira.Issue + issueFields *jira.IssueFields + ) + + BeforeEach(func() { + mockCtrl = gomock.NewController(GinkgoT()) + mockIssueService = jiraMock.NewMockIssueServiceInterface(mockCtrl) + ohssService = NewOHSSService(mockIssueService) + testOHSSID = "OHSS-1000" + issueFields = &jira.IssueFields{Project: jira.Project{Key: JiraOHSSProjectKey}} + testIssue = jira.Issue{ID: testOHSSID, Fields: issueFields} + + }) + + AfterEach(func() { + mockCtrl.Finish() + }) + + Context("When Jira client executes", func() { + It("Should return one issue", func() { + + mockIssueService.EXPECT().Get(testOHSSID, nil).Return(&testIssue, nil, nil).Times(1) + + issue, err := ohssService.GetIssue(testOHSSID) + Expect(err).To(BeNil()) + Expect(issue.ID).To(Equal(testOHSSID)) + Expect(issue.ProjectKey).To(Equal(JiraOHSSProjectKey)) + }) + + It("Should return error for issue not belongs to OHSS project", func() { + + nonOHSSfields := &jira.IssueFields{Project: jira.Project{Key: "NON-OHSS"}} + nonOHSSIssue := jira.Issue{ID: testOHSSID, Fields: nonOHSSfields} + mockIssueService.EXPECT().Get(testOHSSID, nil).Return(&nonOHSSIssue, nil, nil).Times(1) + + _, err := ohssService.GetIssue(testOHSSID) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal("issue OHSS-1000 is not belongs to OHSS project")) + }) + + It("Should return error for empty issue", func() { + + mockIssueService.EXPECT().Get(testOHSSID, nil).Return(nil, nil, nil).Times(1) + + _, err := ohssService.GetIssue(testOHSSID) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal("no matching issue for issueID:OHSS-1000")) + }) + }) +})