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

Check access to GitHub repo on issuetracker initialization #70

Merged
merged 10 commits into from
Oct 23, 2020
23 changes: 23 additions & 0 deletions fetcher/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,26 @@ func (f *Fetcher) Fetch(taskID string) (taskstatus.TaskStatus, error) {

return task.GetStatus(), nil
}

// IsHealthy returns true if the user has access to the repo
func IsHealthy(issueTracker config.IssueTracker, url string) bool {
switch issueTracker {
case config.IssueTrackerGithub:
return healthCheck(url)
default:
return true
}
}

func healthCheck(url string) bool {
res, err := http.Head(url)
if err != nil {
return false
}

if res.StatusCode != http.StatusOK {
return false
}

return true
}
31 changes: 25 additions & 6 deletions issuetracker/issuetracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"github.com/preslavmihaylov/todocheck/issuetracker/models"
)

// ErrUnsupportedHealthCheck is returned when the health check doesn't support the given issue tracker
var ErrUnsupportedHealthCheck = errors.New("unsupported issue tracker for health check")

// TaskFor gets the corresponding task model, based on the issue tracker type
func TaskFor(issueTracker config.IssueTracker) models.Task {
switch issueTracker {
Expand Down Expand Up @@ -53,12 +56,7 @@ func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error)
case config.IssueTrackerJira:
return fmt.Sprintf("%s/rest/api/latest/issue/", origin), nil
case config.IssueTrackerGithub:
tokens := common.RemoveEmptyTokens(strings.Split(origin, "/"))
if tokens[0] == "github.com" {
tokens = append([]string{"https:"}, tokens...)
}

scheme, owner, repo := tokens[0], tokens[2], tokens[3]
scheme, owner, repo := parseGithubDetails(origin)
return fmt.Sprintf("%s//api.github.com/repos/%s/%s/issues/", scheme, owner, repo), nil
case config.IssueTrackerGitlab:
tokens := common.RemoveEmptyTokens(strings.Split(origin, "/"))
Expand Down Expand Up @@ -87,3 +85,24 @@ func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error)
return "", errors.New("unknown issue tracker " + string(issueTracker))
}
}

// HealthcheckURL returns the health check base url given the issue tracker type and the site origin
func HealthcheckURL(issueTracker config.IssueTracker, origin string) (string, error) {
switch issueTracker {
case config.IssueTrackerGithub:
scheme, owner, repo := parseGithubDetails(origin)
return fmt.Sprintf("%s//api.github.com/repos/%s/%s", scheme, owner, repo), nil
default:
return "", ErrUnsupportedHealthCheck
}
}

func parseGithubDetails(origin string) (scheme, owner, repo string) {
tokens := common.RemoveEmptyTokens(strings.Split(origin, "/"))
if !strings.HasPrefix(tokens[0], "http") {
tokens = append([]string{"https:"}, tokens...)
}

scheme, owner, repo = tokens[0], tokens[2], tokens[3]
return
}
2 changes: 2 additions & 0 deletions testing/scenarios/repo_health_checks/.todocheck.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
origin: https://github.com/preslavmihaylov/todocheck
issue_tracker: GITHUB
3 changes: 3 additions & 0 deletions testing/scenarios/repo_health_checks/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

// This is intentionally left blank since we're testing repo access and not todo files
2 changes: 2 additions & 0 deletions testing/test_configs/invalid_github_access.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
origin: https://github.com/user/repo
issue_tracker: GITHUB
2 changes: 2 additions & 0 deletions testing/test_configs/valid_github_access.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
origin: https://github.com/preslavmihaylov/todocheck
issue_tracker: GITHUB
2 changes: 1 addition & 1 deletion testing/test_configs/valid_github_https.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
origin: https://github.com/user/project
origin: https://github.com/preslavmihaylov/todocheck
issue_tracker: GITHUB
2 changes: 1 addition & 1 deletion testing/test_configs/valid_github_origin.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
origin: github.com/user/project
origin: github.com/preslavmihaylov/todocheck
issue_tracker: GITHUB
2 changes: 1 addition & 1 deletion testing/test_configs/valid_github_www.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
origin: www.github.com/user/project
origin: www.github.com/preslavmihaylov/todocheck
issue_tracker: GITHUB
26 changes: 24 additions & 2 deletions testing/todocheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,28 @@ func TestInvalidIssueTracker(t *testing.T) {
}
}

func TestValidGithubAccess(t *testing.T) {
err := scenariobuilder.NewScenario().
WithBinary("../todocheck").
WithBasepath("./scenarios/repo_health_checks").
WithTestEnvConfig("./test_configs/valid_github_access.yaml").
Run()
if err != nil {
t.Errorf("%s", err)
}
preslavmihaylov marked this conversation as resolved.
Show resolved Hide resolved
}

func TestInvalidGithubAccess(t *testing.T) {
preslavmihaylov marked this conversation as resolved.
Show resolved Hide resolved
err := scenariobuilder.NewScenario().
WithBinary("../todocheck").
WithTestEnvConfig("./test_configs/invalid_github_access.yaml").
ExpectExecutionError().
Run()
if err != nil {
t.Errorf("%s", err)
}
}

func TestInvalidOrigins(t *testing.T) {
invalidConfigPaths := []string{
"./test_configs/invalid_github_https.yaml",
Expand Down Expand Up @@ -463,7 +485,7 @@ func TestConfigAutoDetectWithSSHGitConfig(t *testing.T) {
WithBinary("../todocheck").
WithBasepath("./scenarios/auto_detect_config").
WithTestEnvConfig("./scenarios/auto_detect_config/expected_config.yaml").
WithGitConfig("git@github.com:username/repo.git").
WithGitConfig("git@github.com:preslavmihaylov/todocheck.git").
ExpectTodoErr(
scenariobuilder.NewTodoErr().
WithType(errors.TODOErrTypeMalformed).
Expand All @@ -480,7 +502,7 @@ func TestConfigAutoDetectWithHTTPSGitConfig(t *testing.T) {
WithBinary("../todocheck").
WithBasepath("./scenarios/auto_detect_config").
WithTestEnvConfig("./scenarios/auto_detect_config/expected_config.yaml").
WithGitConfig("https://github.com/username/repo").
WithGitConfig("https://github.com/preslavmihaylov/todocheck").
ExpectTodoErr(
scenariobuilder.NewTodoErr().
WithType(errors.TODOErrTypeMalformed).
Expand Down
34 changes: 29 additions & 5 deletions validation/validation.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
package validation

import (
"errors"
"fmt"
"net/url"

"github.com/fatih/color"
"github.com/preslavmihaylov/todocheck/config"
"github.com/preslavmihaylov/todocheck/fetcher"
"github.com/preslavmihaylov/todocheck/issuetracker"
)

// Validate validates the values of given configuration
func Validate(cfg *config.Local) []error {
var errors []error
var errs []error

if err := validateIssueTracker(cfg); err != nil {
errors = append(errors, err)
errs = append(errs, err)
}

if err := validateAuthOfflineURL(cfg); err != nil {
errors = append(errors, err)
errs = append(errs, err)
}

if err := validateIssueTrackerOrigin(cfg); err != nil {
errors = append(errors, err)
errs = append(errs, err)
}

if err := validateIssueTrackerExists(cfg); err != nil {
errs = append(errs, err)
}

if cfg.Auth.Token == "" && cfg.IssueTracker == config.IssueTrackerGithub {
Expand All @@ -31,7 +38,7 @@ func Validate(cfg *config.Local) []error {
" Go to https://developer.github.com/v3/#rate-limiting for more information."))
}

return errors
return errs
}

func validateIssueTracker(cfg *config.Local) error {
Expand All @@ -57,3 +64,20 @@ func validateIssueTrackerOrigin(cfg *config.Local) error {

return nil
}

func validateIssueTrackerExists(cfg *config.Local) error {
url, err := issuetracker.HealthcheckURL(cfg.IssueTracker, cfg.Origin)
if err != nil {
if errors.Is(err, issuetracker.ErrUnsupportedHealthCheck) {
return nil
}

return err
}

if !fetcher.IsHealthy(cfg.IssueTracker, url) {
return fmt.Errorf("repository %s not found. Is the repository private? More info: https://github.com/preslavmihaylov/todocheck#github", url)
}

return nil
}