From 484d2569e6a48d365fcc716b58c6ff6eef15b09c Mon Sep 17 00:00:00 2001 From: Preslav Mihaylov Date: Thu, 30 Jul 2020 16:57:01 +0300 Subject: [PATCH] add support for redmine issue tracker --- README.md | 22 ++++++++++++++- authmanager/authmanager.go | 3 +++ authmanager/authmiddleware/authmiddleware.go | 8 ++++++ config/config.go | 1 + fetcher/fetcher.go | 2 +- issuetracker/{types.go => issuetracker.go} | 18 ++++++++++++- issuetracker/models/pivotaltracker.go | 2 +- issuetracker/models/redmine.go | 28 ++++++++++++++++++++ 8 files changed, 80 insertions(+), 4 deletions(-) rename issuetracker/{types.go => issuetracker.go} (74%) create mode 100644 issuetracker/models/redmine.go diff --git a/README.md b/README.md index fad37bb..1454b66 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ See [How it works](#how-it-works) for more info. * [Gitlab](#gitlab) * [Jira](#jira) * [Pivotal Tracker](#pivotal-tracker) + * [Redmine](#redmine) - [Supported Programming Languages](#supported-programming-languages) - [Ignored Files & Directories](#ignored-files--directories) - [Supported Output Formats](#supported-output-formats) @@ -170,7 +171,7 @@ auth: type: apitoken ``` -The first time you run the application, it will ask for your [API Token](https://www.pivotaltracker.com/help/articles/api_token/): +The first time you run the application, it will ask for your [API Token](https://www.pivotaltracker.com/help/articles/api_token/). After you've specified it, it will store it in the auth tokens cache for subsequent executions. See the [Authentication](#authentication) section for more info. @@ -182,6 +183,25 @@ For example: // TODO #123456: This one is not ``` +## Redmine +To integrate with a redmine issue tracker project, specify the origin of your installation (without project path) and the `REDMINE` issue tracker in your `.todocheck.yaml` configuration. + +You should also specify the `apitoken` as an auth type if you have authentication enabled on your server: +``` +origin: https://redmine.mycorp.com +issue_tracker: REDMINE +auth: + type: apitoken +``` + +Alternatively, if no authentication is required, don't include the `auth` section. + +The first time you run the application, it will ask for your [API Token](https://www.redmine.org/projects/redmine/wiki/rest_api#Authentication). + +After you've specified it, it will store it in the auth tokens cache for subsequent executions. See the [Authentication](#authentication) section for more info. + +In order to integrate todocheck with your redmine server, you'll need to enable the server's rest API and, optionally, enable authentication - [See Docs](https://www.redmine.org/projects/redmine/wiki/rest_api#Authentication). + # Supported Programming Languages Currently, todocheck has parsers for three different types of comments: diff --git a/authmanager/authmanager.go b/authmanager/authmanager.go index 1a46eb2..93e54db 100644 --- a/authmanager/authmanager.go +++ b/authmanager/authmanager.go @@ -16,6 +16,7 @@ const ( githubAPITokenMsg = "Please go to https://github.com/settings/tokens, create a read-only access token & paste it here:\nToken: " gitlabAPITokenMsg = "Please go to https://gitlab.com/profile/personal_access_tokens, create a read-only access token & paste it here:\nToken: " pivotalAPITokenMsg = "Please go to https://www.pivotaltracker.com/profile, create a new API token & paste it here:\nToken: " + redmineAPITokenMsg = "Please go to %s/my/account, create a new API token & paste it here:\nToken: " authTokenEnvVariable = "TODOCHECK_AUTH_TOKEN" ) @@ -43,6 +44,8 @@ func acquireAPIToken(cfg *config.Local) error { msg = gitlabAPITokenMsg } else if cfg.IssueTracker == config.IssueTrackerPivotal { msg = pivotalAPITokenMsg + } else if cfg.IssueTracker == config.IssueTrackerRedmine { + msg = fmt.Sprintf(redmineAPITokenMsg, cfg.Origin) } else { panic("attempt to acquire token for unsupported issue tracker " + cfg.IssueTracker) } diff --git a/authmanager/authmiddleware/authmiddleware.go b/authmanager/authmiddleware/authmiddleware.go index 68f30f3..c7b48d8 100644 --- a/authmanager/authmiddleware/authmiddleware.go +++ b/authmanager/authmiddleware/authmiddleware.go @@ -24,6 +24,8 @@ func For(cfg *config.Local) Func { return gitlabAPITokenMiddleware(cfg.Auth.Token) } else if cfg.IssueTracker == config.IssueTrackerPivotal && cfg.Auth.Type == config.AuthTypeAPIToken { return pivotalTrackerAPITokenMiddleware(cfg.Auth.Token) + } else if cfg.IssueTracker == config.IssueTrackerRedmine && cfg.Auth.Type == config.AuthTypeAPIToken { + return redmineAPITokenMiddleware(cfg.Auth.Token) } panic("couldn't derive authentication middleware based on the given local configuration") @@ -51,6 +53,12 @@ func pivotalTrackerAPITokenMiddleware(token string) Func { } } +func redmineAPITokenMiddleware(token string) Func { + return func(r *http.Request) { + r.Header.Add("X-Redmine-API-Key", token) + } +} + func assertInvariant(condition bool, msg string) { if !condition { panic(msg) diff --git a/config/config.go b/config/config.go index e640043..c3cf7d5 100644 --- a/config/config.go +++ b/config/config.go @@ -22,6 +22,7 @@ const ( IssueTrackerGithub = "GITHUB" IssueTrackerGitlab = "GITLAB" IssueTrackerPivotal = "PIVOTAL_TRACKER" + IssueTrackerRedmine = "REDMINE" ) // Local todocheck configuration struct definition diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index e25cf96..ff4009d 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -26,7 +26,7 @@ func NewFetcher(origin string, tracker config.IssueTracker, authMw authmiddlewar // Fetch a task's status based on task ID func (f *Fetcher) Fetch(taskID string) (taskstatus.TaskStatus, error) { - req, err := http.NewRequest("GET", f.origin+taskID, nil) + req, err := http.NewRequest("GET", f.origin+issuetracker.TaskURLSuffixFor(taskID, f.tracker), nil) if err != nil { return taskstatus.None, fmt.Errorf("failed creating new GET request: %w", err) } diff --git a/issuetracker/types.go b/issuetracker/issuetracker.go similarity index 74% rename from issuetracker/types.go rename to issuetracker/issuetracker.go index cbb900d..5cc8299 100644 --- a/issuetracker/types.go +++ b/issuetracker/issuetracker.go @@ -22,16 +22,30 @@ func TaskFor(issueTracker config.IssueTracker) models.Task { return &models.GitlabTask{} case config.IssueTrackerPivotal: return &models.PivotalTrackerTask{} + case config.IssueTrackerRedmine: + return &models.RedmineTask{} default: return nil } } +// TaskURLSuffixFor the given issue tracker. Returns the appropriate task ID to append to rest api request. +// In most use-cases, the taskID is returned as-is as it is simply appended to the issue tracker URL origin (e.g. issuetracker.com/issues/{taskID}. +// However, some issue trackers might expect the taskID to be appended in a special format +func TaskURLSuffixFor(taskID string, issueTracker config.IssueTracker) string { + switch issueTracker { + case config.IssueTrackerRedmine: + return taskID + ".json" + default: + return taskID + } +} + // BaseURLFor returns the task-fetching base url given the issue tracker type and the site origin func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error) { switch issueTracker { case config.IssueTrackerJira: - return origin + "/rest/api/latest/issue/", nil + return fmt.Sprintf("%s/rest/api/latest/issue/", origin), nil case config.IssueTrackerGithub: tokens := common.RemoveEmptyTokens(strings.Split(origin, "/")) if tokens[0] == "github.com" { @@ -61,6 +75,8 @@ func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error) } return fmt.Sprintf("%s//www.pivotaltracker.com/services/v5/projects/%s/stories/", scheme, project), nil + case config.IssueTrackerRedmine: + return fmt.Sprintf("%s/issues/", origin), nil default: return "", errors.New("unknown issue tracker " + string(issueTracker)) } diff --git a/issuetracker/models/pivotaltracker.go b/issuetracker/models/pivotaltracker.go index 8bfb8bc..51a5494 100644 --- a/issuetracker/models/pivotaltracker.go +++ b/issuetracker/models/pivotaltracker.go @@ -7,7 +7,7 @@ type PivotalTrackerTask struct { CurrentState string `json:"current_state"` } -// GetStatus of picotal tracker task, based on underlying structure +// GetStatus of pivotal tracker task, based on underlying structure func (t *PivotalTrackerTask) GetStatus() taskstatus.TaskStatus { switch t.CurrentState { case "finished": diff --git a/issuetracker/models/redmine.go b/issuetracker/models/redmine.go new file mode 100644 index 0000000..a42a223 --- /dev/null +++ b/issuetracker/models/redmine.go @@ -0,0 +1,28 @@ +package models + +import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus" + +// RedmineTask model +type RedmineTask struct { + Issue struct { + Status struct { + Name string `json:"name"` + } `json:"status"` + } `json:"issue"` +} + +// GetStatus of pivotal tracker task, based on underlying structure +func (t *RedmineTask) GetStatus() taskstatus.TaskStatus { + switch t.Issue.Status.Name { + case "Resolved": + fallthrough + case "Closed": + fallthrough + case "Feedback": + fallthrough + case "Rejected": + return taskstatus.Closed + default: + return taskstatus.Open + } +}