Skip to content
This repository was archived by the owner on Sep 21, 2023. It is now read-only.
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
103 changes: 79 additions & 24 deletions cfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/Sirupsen/logrus"
"github.com/andygrunwald/go-jira"
"github.com/dghubble/oauth1"
"github.com/fsnotify/fsnotify"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -57,6 +58,9 @@ type Config struct {
// log is a logger set up with the configured log level, app name, etc.
log logrus.Entry

// basicAuth represents whether we're using HTTP Basic authentication or OAuth.
basicAuth bool

// fieldIDs is the list of custom fields we pulled from the `fields` JIRA endpoint.
fieldIDs fields

Expand Down Expand Up @@ -105,10 +109,10 @@ func (c *Config) LoadJIRAConfig(client jira.Client) error {
if err != nil {
c.log.Errorf("Error occured trying to read error body: %v", err)
return err
} else {
c.log.Debugf("Error body: %s", body)
return errors.New(string(body))
}

c.log.Debugf("Error body: %s", body)
return errors.New(string(body))
}
c.project = *proj

Expand All @@ -130,6 +134,12 @@ func (c Config) GetConfigString(key string) string {
return c.cmdConfig.GetString(key)
}

// IsBasicAuth is true if we're using HTTP Basic Authentication, and false if
// we're using OAuth.
func (c Config) IsBasicAuth() bool {
return c.basicAuth
}

// GetSinceParam returns the `since` configuration parameter, parsed as a time.Time.
func (c Config) GetSinceParam() time.Time {
return c.since
Expand Down Expand Up @@ -193,14 +203,25 @@ func (c Config) GetRepo() (string, string) {
return parts[0], parts[1]
}

// SetJIRAToken adds the JIRA OAuth tokens in the Viper configuration, ensuring that they
// are saved for future runs.
func (c Config) SetJIRAToken(token *oauth1.Token) {
c.cmdConfig.Set("jira-token", token.Token)
c.cmdConfig.Set("jira-secret", token.TokenSecret)
}

// configFile is a serializable representation of the current Viper configuration.
type configFile struct {
LogLevel string `json:"log-level" mapstructure:"log-level"`
GithubToken string `json:"github-token" mapstructure:"github-token"`
JiraUser string `json:"jira-user" mapstructure:"jira-user"`
JIRAUser string `json:"jira-user" mapstructure:"jira-user"`
JIRAToken string `json:"jira-token" mapstructure:"jira-token"`
JIRASecret string `json:"jira-secret" mapstructure:"jira-secret"`
JIRAKey string `json:"jira-private-key" mapstructure:"jira-private-key"`
JIRACKey string `json:"jira-consumer-key" mapstructure:"jira-consumer-key"`
RepoName string `json:"repo-name" mapstructure:"repo-name"`
JiraUri string `json:"jira-uri" mapstructure:"jira-uri"`
JiraProject string `json:"jira-project" mapstructure:"jira-project"`
JIRAURI string `json:"jira-uri" mapstructure:"jira-uri"`
JIRAProject string `json:"jira-project" mapstructure:"jira-project"`
Since string `json:"since" mapstructure:"since"`
Timeout time.Duration `json:"timeout" mapstructure:"timeout"`
}
Expand Down Expand Up @@ -246,6 +267,7 @@ func newViper(appName, cfgFile string) *viper.Viper {
if cfgFile != "" {
v.SetConfigFile(cfgFile)
}
v.SetConfigType("json")

if err := v.ReadInConfig(); err == nil {
log.WithField("file", v.ConfigFileUsed()).Infof("config file loaded")
Expand Down Expand Up @@ -300,7 +322,7 @@ func newLogger(app, level string) *logrus.Entry {
// real URI, etc. This is the first level of checking. It does not confirm
// if a JIRA cli is running at `jira-uri` for example; that is checked
// in getJIRAClient when we actually make a call to the API.
func (c Config) validateConfig() error {
func (c *Config) validateConfig() error {
// Log level and config file location are validated already

c.log.Debug("Checking config variables...")
Expand All @@ -309,20 +331,53 @@ func (c Config) validateConfig() error {
return errors.New("GitHub token required")
}

jUser := c.cmdConfig.GetString("jira-user")
if jUser == "" {
return errors.New("Jira username required")
}
c.basicAuth = (c.cmdConfig.GetString("jira-user") != "") && (c.cmdConfig.GetString("jira-pass") != "")

if c.basicAuth {
c.log.Debug("Using HTTP Basic Authentication")

jUser := c.cmdConfig.GetString("jira-user")
if jUser == "" {
return errors.New("Jira username required")
}

jPass := c.cmdConfig.GetString("jira-pass")
if jPass == "" {
fmt.Print("Enter your JIRA password: ")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh funny, fmt.Print is almost never used. I had to double check that it was a real func.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? That's how I've always done asking for input in a CLI program. Is it usually done differently in Go?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nono, just that normally people use fmt.Println or fmt.Printf, but simply Print gets relatively little use.

bytePass, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return errors.New("JIRA password required")
}
fmt.Println()
c.cmdConfig.Set("jira-pass", string(bytePass))
}
} else {
c.log.Debug("Using OAuth 1.0a authentication")

token := c.cmdConfig.GetString("jira-token")
if token == "" {
return errors.New("JIRA access token required")
}

secret := c.cmdConfig.GetString("jira-secret")
if secret == "" {
return errors.New("JIRA access token secret required")
}

consumerKey := c.cmdConfig.GetString("jira-consumer-key")
if consumerKey == "" {
return errors.New("JIRA consumer key required for OAuth handshake")
}

privateKey := c.cmdConfig.GetString("jira-private-key")
if privateKey == "" {
return errors.New("JIRA private key required for OAuth handshake")
}

jPass := c.cmdConfig.GetString("jira-pass")
if jPass == "" {
fmt.Print("Enter your JIRA password: ")
bytePass, err := terminal.ReadPassword(int(syscall.Stdin))
_, err := os.Open(privateKey)
if err != nil {
return errors.New("Jira password required")
return errors.New("JIRA private key must point to existing PEM file")
}
fmt.Println()
c.cmdConfig.Set("jira-pass", string(bytePass))
}

repo := c.cmdConfig.GetString("repo-name")
Expand Down Expand Up @@ -417,17 +472,17 @@ func (c Config) getFieldIDs(client jira.Client) (fields, error) {
}

if fieldIDs.githubID == "" {
return fieldIDs, errors.New("Could not find ID of 'GitHub ID' custom field. Check that it is named correctly.")
return fieldIDs, errors.New("could not find ID of 'GitHub ID' custom field; check that it is named correctly")
} else if fieldIDs.githubNumber == "" {
return fieldIDs, errors.New("Could not find ID of 'GitHub Number' custom field. Check that it is named correctly.")
return fieldIDs, errors.New("could not find ID of 'GitHub Number' custom field; check that it is named correctly")
} else if fieldIDs.githubLabels == "" {
return fieldIDs, errors.New("Could not find ID of 'Github Labels' custom field. Check that it is named correctly.")
return fieldIDs, errors.New("could not find ID of 'Github Labels' custom field; check that it is named correctly")
} else if fieldIDs.githubStatus == "" {
return fieldIDs, errors.New("Could not find ID of 'Github Status' custom field. Check that it is named correctly.")
return fieldIDs, errors.New("could not find ID of 'Github Status' custom field; check that it is named correctly")
} else if fieldIDs.githubReporter == "" {
return fieldIDs, errors.New("Could not find ID of 'Github Reporter' custom field. Check that it is named correctly.")
return fieldIDs, errors.New("could not find ID of 'Github Reporter' custom field; check that it is named correctly")
} else if fieldIDs.lastUpdate == "" {
return fieldIDs, errors.New("Could not find ID of 'Last Issue-Sync Update' custom field. Check that it is named correctly.")
return fieldIDs, errors.New("could not find ID of 'Last Issue-Sync Update' custom field; check that it is named correctly")
}

c.log.Debug("All fields have been checked.")
Expand Down
11 changes: 9 additions & 2 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ import:
version: v3
- package: github.com/cenkalti/backoff
version: v1.0.0
- package: github.com/dghubble/oauth1
version: v0.4.0
8 changes: 4 additions & 4 deletions lib/clients/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (g realGHClient) ListIssues() ([]*github.Issue, error) {
ghIssues, ok := i.([]*github.Issue)
if !ok {
log.Errorf("Get GitHub issues did not return issues! Got: %v", i)
return nil, errors.New(fmt.Sprintf("Get GitHub issues failed: expected []*github.Issue; got %T", i))
return nil, fmt.Errorf("Get GitHub issues failed: expected []*github.Issue; got %T", i)
}

log.Debug("Collected all GitHub issues")
Expand Down Expand Up @@ -80,7 +80,7 @@ func (g realGHClient) ListComments(issue github.Issue) ([]*github.IssueComment,
comments, ok := c.([]*github.IssueComment)
if !ok {
log.Errorf("Get GitHub comments did not return comments! Got: %v", c)
return nil, errors.New(fmt.Sprintf("Get GitHub comments failed: expected []*github.IssueComment; got %T", c))
return nil, fmt.Errorf("Get GitHub comments failed: expected []*github.IssueComment; got %T", c)
}

return comments, nil
Expand All @@ -101,7 +101,7 @@ func (g realGHClient) GetUser(login string) (github.User, error) {
user, ok := u.(*github.User)
if !ok {
log.Errorf("Get GitHub user did not return user! Got: %v", u)
return github.User{}, errors.New(fmt.Sprintf("Get GitHub user failed: expected *github.User; got %T", u))
return github.User{}, fmt.Errorf("Get GitHub user failed: expected *github.User; got %T", u)
}

return *user, nil
Expand All @@ -124,7 +124,7 @@ func (g realGHClient) GetRateLimits() (github.RateLimits, error) {
rate, ok := rl.(*github.RateLimits)
if !ok {
log.Errorf("Get GitHub rate limits did not return rate limits! Got: %v", rl)
return github.RateLimits{}, errors.New(fmt.Sprintf("Get GitHub rate limits failed: expected *github.RateLimits; got %T", rl))
return github.RateLimits{}, fmt.Errorf("Get GitHub rate limits failed: expected *github.RateLimits; got %T", rl)
}

return *rate, nil
Expand Down
39 changes: 26 additions & 13 deletions lib/clients/jira.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"regexp"

"github.com/andygrunwald/go-jira"
Expand All @@ -27,10 +28,9 @@ func getErrorBody(config cfg.Config, res *jira.Response) error {
if err != nil {
log.Errorf("Error occured trying to read error body: %v", err)
return err
} else {
log.Debugf("Error body: %s", body)
return errors.New(string(body))
}
log.Debugf("Error body: %s", body)
return errors.New(string(body))
}

// JIRAClient is a wrapper around the JIRA API clients library we
Expand All @@ -53,14 +53,27 @@ type JIRAClient interface {
func NewJIRAClient(config *cfg.Config) (JIRAClient, error) {
log := config.GetLogger()

var oauth *http.Client
var err error
if !config.IsBasicAuth() {
oauth, err = newJIRAHTTPClient(*config)
if err != nil {
log.Errorf("Error getting OAuth config: %v", err)
return dryrunJIRAClient{}, err
}
}

var j JIRAClient

client, err := jira.NewClient(nil, config.GetConfigString("jira-uri"))
client, err := jira.NewClient(oauth, config.GetConfigString("jira-uri"))
if err != nil {
log.Errorf("Error initializing JIRA clients; check your base URI. Error: %v", err)
return dryrunJIRAClient{}, err
}
client.Authentication.SetBasicAuth(config.GetConfigString("jira-user"), config.GetConfigString("jira-pass"))

if config.IsBasicAuth() {
client.Authentication.SetBasicAuth(config.GetConfigString("jira-user"), config.GetConfigString("jira-pass"))
}

log.Debug("JIRA clients initialized")

Expand Down Expand Up @@ -108,7 +121,7 @@ func (j realJIRAClient) ListIssues(ids string) ([]jira.Issue, error) {
jiraIssues, ok := ji.([]jira.Issue)
if !ok {
log.Errorf("Get JIRA issues did not return issues! Got: %v", ji)
return nil, errors.New(fmt.Sprintf("Get JIRA issues failed: expected []jira.Issue; got %T", ji))
return nil, fmt.Errorf("Get JIRA issues failed: expected []jira.Issue; got %T", ji)
}

return jiraIssues, nil
Expand All @@ -129,7 +142,7 @@ func (j realJIRAClient) GetIssue(key string) (jira.Issue, error) {
issue, ok := i.(*jira.Issue)
if !ok {
log.Errorf("Get JIRA issue did not return issue! Got %v", i)
return jira.Issue{}, errors.New(fmt.Sprintf("Get JIRA issue failed: expected *jira.Issue; got %T", i))
return jira.Issue{}, fmt.Errorf("Get JIRA issue failed: expected *jira.Issue; got %T", i)
}

return *issue, nil
Expand All @@ -151,7 +164,7 @@ func (j realJIRAClient) CreateIssue(issue jira.Issue) (jira.Issue, error) {
is, ok := i.(*jira.Issue)
if !ok {
log.Errorf("Create JIRA issue did not return issue! Got: %v", i)
return jira.Issue{}, errors.New(fmt.Sprintf("Create JIRA issue failed: expected *jira.Issue; got %T", i))
return jira.Issue{}, fmt.Errorf("Create JIRA issue failed: expected *jira.Issue; got %T", i)
}

// The JIRA create endpoint doesn't return more in the issue than just
Expand Down Expand Up @@ -180,7 +193,7 @@ func (j realJIRAClient) UpdateIssue(issue jira.Issue) (jira.Issue, error) {
is, ok := i.(*jira.Issue)
if !ok {
log.Errorf("Update JIRA issue did not return issue! Got: %v", i)
return jira.Issue{}, errors.New(fmt.Sprintf("Update JIRA issue failed: expected *jira.Issue; got %T", i))
return jira.Issue{}, fmt.Errorf("Update JIRA issue failed: expected *jira.Issue; got %T", i)
}

// The JIRA update endpoint doesn't return more in the issue than just
Expand Down Expand Up @@ -227,7 +240,7 @@ func (j realJIRAClient) CreateComment(issue jira.Issue, comment github.IssueComm
co, ok := com.(*jira.Comment)
if !ok {
log.Errorf("Create JIRA comment did not return comment! Got: %v", com)
return jira.Comment{}, errors.New(fmt.Sprintf("Create JIRA comment failed: expected *jira.Comment; got %T", com))
return jira.Comment{}, fmt.Errorf("Create JIRA comment failed: expected *jira.Comment; got %T", com)
}
return *co, nil
}
Expand Down Expand Up @@ -279,7 +292,7 @@ func (j realJIRAClient) UpdateComment(issue jira.Issue, id string, comment githu
co, ok := com.(*jira.Comment)
if !ok {
log.Errorf("Update JIRA comment did not return comment! Got: %v", com)
return jira.Comment{}, errors.New(fmt.Sprintf("Update JIRA comment failed: expected *jira.Comment; got %T", com))
return jira.Comment{}, fmt.Errorf("Update JIRA comment failed: expected *jira.Comment; got %T", com)
}
return *co, nil
}
Expand Down Expand Up @@ -359,7 +372,7 @@ func (j dryrunJIRAClient) ListIssues(ids string) ([]jira.Issue, error) {
jiraIssues, ok := ji.([]jira.Issue)
if !ok {
log.Errorf("Get JIRA issues did not return issues! Got: %v", ji)
return nil, errors.New(fmt.Sprintf("Get JIRA issues failed: expected []jira.Issue; got %T", ji))
return nil, fmt.Errorf("Get JIRA issues failed: expected []jira.Issue; got %T", ji)
}

return jiraIssues, nil
Expand All @@ -382,7 +395,7 @@ func (j dryrunJIRAClient) GetIssue(key string) (jira.Issue, error) {
issue, ok := i.(*jira.Issue)
if !ok {
log.Errorf("Get JIRA issue did not return issue! Got %v", i)
return jira.Issue{}, errors.New(fmt.Sprintf("Get JIRA issue failed: expected *jira.Issue; got %T", i))
return jira.Issue{}, fmt.Errorf("Get JIRA issue failed: expected *jira.Issue; got %T", i)
}

return *issue, nil
Expand Down
Loading