├── .checks-out ├── .gitignore ├── .travis.yml ├── .whitesource ├── CHANGELOG.md ├── CODEOWNERS ├── ENVIRONMENT_VARS.md ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── admin └── browse.go ├── api ├── admin.go ├── config.go ├── json.go ├── maintainer.go ├── orgs.go ├── orgs_test.go ├── repos.go ├── slack.go ├── stats.go ├── user_test.go ├── users.go └── validate.go ├── cache ├── cache.go ├── cache_test.go └── context.go ├── cmd └── integration │ └── main.go ├── docs ├── .gitkeep ├── api │ └── index.html ├── approvals │ └── index.html ├── branches │ └── index.html ├── categories │ └── index.xml ├── customize │ └── index.html ├── docs.css ├── faq │ └── index.html ├── images │ ├── app_registration.png │ ├── approval_complete.png │ ├── hook_error.png │ ├── hook_success.png │ ├── maintainers_team.png │ ├── pending_approval.png │ └── protected_branches.png ├── index.html ├── index.xml ├── install │ └── index.html ├── maintainers │ └── index.html ├── overview │ └── index.html ├── sitemap.xml ├── style.css ├── support │ └── index.html └── tags │ └── index.xml ├── envvars ├── envvars.go └── envvars_test.go ├── example.MAINTAINERS ├── example.checks-out ├── exterror ├── exterror.go └── exterror_test.go ├── go.mod ├── go.sum ├── hjson └── hjson.go ├── hugo ├── config.toml ├── content │ ├── api.md │ ├── approvals.md │ ├── branches.md │ ├── customize.md │ ├── faq.md │ ├── install.md │ ├── maintainers.md │ ├── overview.md │ └── support.md ├── layouts │ └── index.html ├── static │ ├── docs.css │ ├── images │ │ ├── app_registration.png │ │ ├── approval_complete.png │ │ ├── hook_error.png │ │ ├── hook_success.png │ │ ├── maintainers_team.png │ │ ├── pending_approval.png │ │ └── protected_branches.png │ └── style.css └── themes │ └── default │ ├── layouts │ └── _default │ │ └── single.html │ └── static │ └── .gitkeep ├── logstats └── logstats.go ├── main.go ├── matcher ├── language_test.go ├── parser.go ├── parser_test.go └── scanner.go ├── migration └── migration.go ├── model ├── approval.go ├── approval_op.go ├── approval_test.go ├── approval_validate.go ├── audit.go ├── branch.go ├── capabilities.go ├── comment.go ├── commentsupport.go ├── commit.go ├── commitfile.go ├── commitrange.go ├── commitrange_enumer.go ├── commitstatus.go ├── config.go ├── config_test.go ├── deployment.go ├── deployment_test.go ├── feedback.go ├── feedback_enumer.go ├── issue.go ├── maintainer.go ├── matcher_translator.go ├── matchpolicy.go ├── matchpolicy_test.go ├── merge.go ├── org.go ├── repo.go ├── review.go ├── scopepolicy.go ├── scopepolicy_test.go ├── semver.go ├── semver_enumer.go ├── snapshot.go ├── tag.go ├── tag_test.go ├── user.go └── util.go ├── notifier ├── context.go ├── github │ └── github.go ├── notifier.go ├── slack │ └── slack.go └── types.go ├── remote ├── cache.go ├── cache_test.go ├── context.go ├── github │ ├── client.go │ ├── github.go │ ├── github_integration_test.go │ ├── paging.go │ ├── types.go │ ├── utils.go │ └── walk.go ├── remote.go └── types.go ├── router ├── middleware │ ├── access │ │ └── access.go │ ├── cache.go │ ├── exterror.go │ ├── header │ │ └── header.go │ ├── logrequests.go │ ├── recovery.go │ ├── remote.go │ ├── session │ │ ├── capabilities.go │ │ └── user.go │ ├── store.go │ └── version.go └── router.go ├── set ├── lowerset.go ├── lowerset_test.go ├── set.go └── set_test.go ├── shared ├── httputil │ └── httputil.go └── token │ └── token.go ├── snapshot ├── errors.go ├── maintainer.go ├── maintainer_hjson.go ├── maintainer_test.go ├── maintainer_text.go ├── maintainer_toml.go ├── snapshot.go ├── snapshot_test.go └── validate.go ├── store ├── context.go ├── datastore │ ├── datastore.go │ ├── datastore_test.go │ ├── repos.go │ ├── repos_test.go │ ├── slack.go │ ├── users.go │ └── users_test.go ├── migration │ ├── migration.go │ ├── migration_gen.go │ ├── mysql │ │ ├── 001_init.sql │ │ ├── 002_org.sql │ │ ├── 003_drop_emails.sql │ │ ├── 004_limit_users.sql │ │ ├── 005_oauth_scope.sql │ │ ├── 006_add_orgs_table.sql │ │ └── 007_add_slack_urls.sql │ ├── postgres │ │ ├── 001_init.sql │ │ ├── 002_org.sql │ │ ├── 003_drop_emails.sql │ │ ├── 004_limit_users.sql │ │ ├── 005_oauth_scope.sql │ │ ├── 006_add_orgs_table.sql │ │ └── 007_add_slack_urls.sql │ └── sqlite3 │ │ ├── 001_init.sql │ │ ├── 002_org.sql │ │ ├── 003_drop_emails.sql │ │ ├── 004_limit_users.sql │ │ ├── 005_oauth_scope.sql │ │ ├── 006_add_orgs_table.sql │ │ └── 007_add_slack_urls.sql ├── store.go └── store_test.go ├── strings ├── lowercase │ ├── lowercase.go │ └── lowercase_test.go ├── miniglob │ ├── miniglob.go │ └── miniglob_test.go └── rxserde │ ├── regexp.go │ └── regexp_test.go ├── swagger.yaml ├── usage ├── usage.go └── usage_test.go ├── version └── version.go └── web ├── approval.go ├── approval_hook.go ├── approval_test.go ├── audit.go ├── comment_hook.go ├── endpoints.go ├── github_create.go ├── hook.go ├── index.go ├── login.go ├── login_test.go ├── merge.go ├── notification.go ├── pr_hook.go ├── remote.go ├── repo_hook.go ├── review_hook.go ├── static ├── files │ ├── angular-toggle-switch.min.js │ ├── checksout.html │ ├── checksout.js │ ├── favicon.ico │ ├── images │ │ ├── maintainers.png │ │ ├── meowser.png │ │ ├── pending_approval.png │ │ └── received_approval.png │ ├── logo.svg │ ├── main_styles.css │ ├── styles.css │ └── toggle_switch.css ├── static.go └── static_gen.go ├── status_hook.go ├── status_hook_test.go ├── store.go ├── tag.go ├── template ├── files │ ├── brand.html │ ├── error.html │ ├── index.html │ └── logout.html ├── template.go └── template_gen.go └── version.go /.checks-out: -------------------------------------------------------------------------------- 1 | approvals: 2 | # This is the array of approval policies. 3 | # 4 | # For each pull request we traverse the array 5 | # of approval policies until we find the first policy 6 | # that matches. Note that the approval policy is 7 | # an ordered list. You should specify your most specific 8 | # policies first and your least specific policies last. 9 | # 10 | # An approval policy will match based 11 | # on the criteria in the "scope" section. You can 12 | # match based on the pull request base branch or 13 | # based on the file paths of the modified files. 14 | # 15 | # The most common configuration will have only 16 | # a single approval policy. The last approval policy 17 | # is required to have an empty "scope" section. 18 | # The last policy is the default approval policy. This 19 | # policy will be applied when none of the other policies 20 | # have matched. 21 | [ 22 | { 23 | # 24 | # The name is strictly for human consumption. 25 | # It is used by the comment section. It is optional. 26 | # 27 | name: "dev" 28 | # 29 | # Limit this policy to pull requests that have 30 | # base branch 'dev'. The base branch is where the 31 | # changes should be applied. 32 | # 33 | scope: 34 | { 35 | branches: [ "dev", "qa" ] 36 | } 37 | # 38 | # Require 1 approver from the MAINTAINERS file. 39 | # Allow self-approval. 40 | # 41 | match: "all[count=1,self=true]" 42 | # 43 | # Apply a git tag after auto-merging the pull request. 44 | # This feature is disabled by default. 45 | tag: 46 | { 47 | enable: true 48 | template: "{{.Version}}-dev" 49 | increment: "patch" 50 | } 51 | } 52 | { 53 | name: "master" 54 | scope: 55 | { 56 | branches: [ "master" ] 57 | } 58 | # 59 | # Use this approval pattern instead of the default pattern. 60 | # 61 | pattern: "(?i)^shipit\\s*(?P\\S*)" 62 | # 63 | # Require 1 approver from the MAINTAINERS file. 64 | # Deny self-approval. 65 | # 66 | match: "all[count=1,self=false]" 67 | tag: 68 | { 69 | enable: true 70 | template: "{{.Version}}" 71 | increment: "minor" 72 | } 73 | } 74 | # Disable service on remaining branches 75 | # The remaining branches are feature branches 76 | # and do not require approval. 77 | # 78 | { 79 | match: "off" 80 | } 81 | ] 82 | # 83 | # Block a pull request with this pull request comment. 84 | # This feature is disabled by default. 85 | # 86 | antipattern: "(?i)^holdit" 87 | # 88 | # Automatically merge the pull request when all status checks pass. 89 | # This feature is disabled by default. 90 | # 91 | merge: 92 | { 93 | enable: true 94 | } 95 | # 96 | # Report the status of pull request to various comment sinks. 97 | # This feature is disabled by default. 98 | # 99 | comment: 100 | { 101 | enable: true 102 | targets: [ 103 | { 104 | target: github 105 | types: [ "approve", "block", "reset" ] 106 | } 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | checks-out 3 | checks-out.sqlite 4 | .DS_Store 5 | .env 6 | .idea 7 | .vscode 8 | report.out 9 | report.xml 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.11.x" 4 | 5 | script: env GO111MODULE=on make test 6 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence, 6 | # @global-owner1 and @global-owner2 will be requested for 7 | # review when someone opens a pull request. 8 | * @jonbodner @jonathana 9 | 10 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | github-org repo-self 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Propagate git tag to version identifier 2 | VERSION_TAG:=$(shell git describe --abbrev=0 --tags) 3 | ifeq ($(VERSION_TAG),) 4 | VERSION_TAG:=noversion 5 | endif 6 | # Propagate git revision number to version identifier 7 | VERSION:=${VERSION_TAG}+$(shell git rev-parse --short HEAD) 8 | 9 | # directories and remove vendor directory 10 | # used for running unit tests 11 | NOVENDOR := $(shell go list -e ./... | grep -v /vendor/) 12 | 13 | # files and remove vendor directory, auto generated files, and mock files 14 | # used for static analysis, code linting, and code formatting 15 | NOVENDOR_FILES := $(shell find . -name "*.go" | grep -v /vendor/ | grep -v /mock/ | grep -v "_gen\.go" ) 16 | 17 | all: build 18 | 19 | gen: gen-assets gen-migration 20 | 21 | gen-assets: 22 | go generate github.com/capitalone/checks-out/web/static 23 | go generate github.com/capitalone/checks-out/web/template 24 | 25 | gen-migration: 26 | go generate github.com/capitalone/checks-out/store/migration 27 | 28 | build: 29 | @# static linking sqlite seems to break DNS 30 | go build --ldflags '-X github.com/capitalone/checks-out/version.Version=$(VERSION)' -o checks-out 31 | go test -run ^$$ $(NOVENDOR) > /dev/null 32 | 33 | test: vet 34 | @# use redirect instead of tee to preserve exit code 35 | go test -short -p=1 -cover $(NOVENDOR) -v > report.out; \ 36 | code=$$?; \ 37 | cat report.out; \ 38 | grep -e 'FAIL' report.out; \ 39 | exit $${code} 40 | 41 | test-complete: vet 42 | @# use redirect instead of tee to preserve exit code 43 | GITHUB_TEST_ENABLE=1 go test -short -p=1 -cover $(NOVENDOR) -v > report.out; \ 44 | code=$$?; \ 45 | cat report.out; \ 46 | grep -e 'FAIL' report.out; \ 47 | exit $${code} 48 | 49 | .PHONY: test-cover-html 50 | 51 | test-cover-html: 52 | echo "mode: count" > coverage-all.out 53 | echo "Packages: $(NOVENDOR)" 54 | echo "VERSION: $(VERSION)" 55 | $(foreach pkg,$(NOVENDOR), \ 56 | echo ${pkg}; \ 57 | go test -coverprofile=coverage.out -covermode=count ${pkg}; \ 58 | tail -n +2 coverage.out >> coverage-all.out;) 59 | go tool cover -html=coverage-all.out -o coverage.html 60 | 61 | fmt: 62 | for FILE in $(NOVENDOR_FILES); do go fmt $$FILE; done; 63 | 64 | vet: get-modules 65 | @echo 'Running go tool vet -shadow' 66 | @for FILE in $(NOVENDOR_FILES); do go tool vet -shadow $$FILE || exit 1; done; 67 | 68 | lint: get-modules 69 | for FILE in $(NOVENDOR_FILES); do golint $$FILE; done; 70 | 71 | clean: 72 | rm -f checks-out report.out 73 | 74 | get-modules: 75 | go mod download 76 | go mod verify 77 | 78 | test-mysql: 79 | DB_DRIVER="mysql" DB_SOURCE="root@tcp(127.0.0.1:3306)/test?parseTime=true" go test -v -cover github.com/capitalone/checks-out/store/datastore 80 | -------------------------------------------------------------------------------- /admin/browse.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package admin 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | 25 | jsonpointer "github.com/mattn/go-jsonpointer" 26 | "github.com/capitalone/checks-out/envvars" 27 | "github.com/capitalone/checks-out/hjson" 28 | "github.com/capitalone/checks-out/model" 29 | "github.com/capitalone/checks-out/remote" 30 | ) 31 | 32 | var configFileName = fmt.Sprintf(".%s", envvars.Env.Branding.Name) 33 | 34 | func GetConfigSubtree(c context.Context, r *model.Repo, u *model.User, path string) interface{} { 35 | rcfile, err := remote.GetContents(c, u, r, configFileName) 36 | if err != nil { 37 | return err.Error() 38 | } 39 | var config map[string]interface{} 40 | err = hjson.Unmarshal(rcfile, &config) 41 | if err != nil { 42 | return err.Error() 43 | } 44 | if !jsonpointer.Has(config, path) { 45 | return nil 46 | } 47 | resp, err2 := jsonpointer.Get(config, path) 48 | if err2 != nil { 49 | return err2.Error() 50 | } 51 | return resp 52 | } 53 | -------------------------------------------------------------------------------- /api/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package api 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/capitalone/checks-out/envvars" 25 | "github.com/capitalone/checks-out/exterror" 26 | "github.com/capitalone/checks-out/router/middleware/session" 27 | "github.com/capitalone/checks-out/snapshot" 28 | "github.com/capitalone/checks-out/store" 29 | 30 | "github.com/gin-gonic/gin" 31 | ) 32 | 33 | var configFileName = fmt.Sprintf(".%s", envvars.Env.Branding.Name) 34 | 35 | // GetConfig gets the parsed configuration file. 36 | func GetConfig(c *gin.Context) { 37 | var ( 38 | owner = c.Param("owner") 39 | name = c.Param("repo") 40 | user = session.User(c) 41 | caps = session.Capability(c) 42 | ) 43 | repo, err := store.GetRepoOwnerName(c, owner, name) 44 | if err != nil { 45 | msg := fmt.Sprintf("Getting repository %s", name) 46 | c.Error(exterror.Append(err, msg)) 47 | return 48 | } 49 | config, err := snapshot.GetConfig(c, user, caps, repo) 50 | if err != nil { 51 | msg := fmt.Sprintf("Getting %s configuration for %s", configFileName, name) 52 | c.Error(exterror.Append(err, msg)) 53 | } else { 54 | IndentedJSON(c, 200, config) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/json.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package api 20 | 21 | import ( 22 | "bytes" 23 | "encoding/json" 24 | 25 | log "github.com/Sirupsen/logrus" 26 | "github.com/gin-gonic/gin" 27 | ) 28 | 29 | func IndentedJSON(c *gin.Context, code int, obj interface{}) { 30 | var buf bytes.Buffer 31 | e := json.NewEncoder(&buf) 32 | e.SetEscapeHTML(false) 33 | e.SetIndent("", " ") 34 | err := e.Encode(obj) 35 | if err != nil { 36 | log.Errorf("JSON encoding error %+v. %s", obj, err) 37 | c.String(500, "JSON encoding error") 38 | } else { 39 | c.Header("Content-Type", "application/json; charset=utf-8") 40 | c.String(code, string(buf.Bytes())) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/maintainer.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package api 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/capitalone/checks-out/exterror" 25 | "github.com/capitalone/checks-out/router/middleware/session" 26 | "github.com/capitalone/checks-out/snapshot" 27 | "github.com/capitalone/checks-out/store" 28 | 29 | "github.com/gin-gonic/gin" 30 | ) 31 | 32 | // GetMaintainer gets the MAINTAINER configuration file. 33 | func GetMaintainer(c *gin.Context) { 34 | var ( 35 | owner = c.Param("owner") 36 | name = c.Param("repo") 37 | user = session.User(c) 38 | caps = session.Capability(c) 39 | ) 40 | repo, err := store.GetRepoOwnerName(c, owner, name) 41 | if err != nil { 42 | msg := fmt.Sprintf("Getting repository %s", name) 43 | c.Error(exterror.Append(err, msg)) 44 | return 45 | } 46 | _, maintainer, err := snapshot.GetConfigAndMaintainers(c, user, caps, repo) 47 | if err != nil { 48 | msg := fmt.Sprintf("Getting maintainer file for %s", name) 49 | c.Error(exterror.Append(err, msg)) 50 | return 51 | } 52 | _, err = maintainer.PersonToOrg() 53 | if err != nil { 54 | msg := fmt.Sprintf("Expanding maintainer file for %s", name) 55 | c.Error(exterror.Append(err, msg)) 56 | return 57 | } 58 | IndentedJSON(c, 200, maintainer) 59 | 60 | } 61 | -------------------------------------------------------------------------------- /api/stats.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package api 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/usage" 23 | 24 | "github.com/gin-gonic/gin" 25 | ) 26 | 27 | func AdminStats(c *gin.Context) { 28 | stats := usage.GetStats() 29 | IndentedJSON(c, 200, stats) 30 | } 31 | -------------------------------------------------------------------------------- /api/user_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package api 20 | 21 | import ( 22 | "encoding/json" 23 | "io/ioutil" 24 | "net/http" 25 | "net/http/httptest" 26 | "strings" 27 | "testing" 28 | 29 | "github.com/capitalone/checks-out/model" 30 | 31 | "github.com/Sirupsen/logrus" 32 | "github.com/franela/goblin" 33 | "github.com/gin-gonic/gin" 34 | ) 35 | 36 | func TestUsers(t *testing.T) { 37 | gin.SetMode(gin.TestMode) 38 | logrus.SetOutput(ioutil.Discard) 39 | 40 | g := goblin.Goblin(t) 41 | 42 | g.Describe("User endpoint", func() { 43 | g.It("Should return the authenticated user", func() { 44 | 45 | e := gin.New() 46 | e.NoRoute(GetUser) 47 | e.Use(func(c *gin.Context) { 48 | c.Set("user", fakeUser) 49 | }) 50 | 51 | w := httptest.NewRecorder() 52 | r, _ := http.NewRequest("GET", "/", nil) 53 | e.ServeHTTP(w, r) 54 | 55 | want, _ := json.MarshalIndent(fakeUser, "", " ") 56 | got := strings.TrimSpace(w.Body.String()) 57 | g.Assert(got).Equal(string(want)) 58 | g.Assert(w.Code).Equal(200) 59 | }) 60 | }) 61 | } 62 | 63 | var ( 64 | fakeUser = &model.User{Login: "octocat"} 65 | fakeOrgs = []*model.GitHubOrg{ 66 | {Login: "drone"}, 67 | {Login: "docker"}, 68 | } 69 | ) 70 | -------------------------------------------------------------------------------- /api/users.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package api 20 | 21 | import ( 22 | "database/sql" 23 | "net/http" 24 | 25 | "github.com/capitalone/checks-out/exterror" 26 | "github.com/capitalone/checks-out/remote" 27 | "github.com/capitalone/checks-out/router/middleware/session" 28 | "github.com/capitalone/checks-out/store" 29 | 30 | "github.com/gin-gonic/gin" 31 | ) 32 | 33 | // GetUser gets the currently authenticated user. 34 | func GetUser(c *gin.Context) { 35 | IndentedJSON(c, 200, session.User(c)) 36 | } 37 | 38 | // DeleteUser removes the currently authenticated user 39 | // and all associated repositories from the database. 40 | func DeleteUser(c *gin.Context) { 41 | user := session.User(c) 42 | repos, err := store.GetRepoUserId(c, user.ID) 43 | if err != nil { 44 | c.Error(exterror.Append(err, "Deleting user")) 45 | return 46 | } 47 | for _, repo := range repos { 48 | err = store.DeleteRepo(c, repo) 49 | if err != nil { 50 | c.Error(exterror.Append(err, "Deleting user")) 51 | return 52 | } 53 | } 54 | err = store.DeleteUser(c, user) 55 | if err != nil { 56 | c.Error(exterror.Append(err, "Deleting user")) 57 | return 58 | } 59 | err = remote.RevokeAuthorization(c, user) 60 | if err != nil { 61 | c.Error(exterror.Append(err, "Deleting user")) 62 | return 63 | } 64 | c.String(204, "") 65 | } 66 | 67 | func GetReposForUserLogin(c *gin.Context) { 68 | var ( 69 | login = c.Param("user") 70 | ) 71 | user, err := store.GetUserLogin(c, login) 72 | if err != nil { 73 | if err == sql.ErrNoRows { 74 | err = exterror.Create(http.StatusNotFound, err) 75 | } 76 | c.Error(err) 77 | return 78 | } 79 | repos, err := store.GetRepoUserId(c, user.ID) 80 | if err != nil { 81 | if err == sql.ErrNoRows { 82 | err = exterror.Create(http.StatusNotFound, err) 83 | } 84 | c.Error(err) 85 | return 86 | } 87 | IndentedJSON(c, 200, repos) 88 | } 89 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package cache 20 | 21 | import ( 22 | "context" 23 | "time" 24 | 25 | "github.com/koding/cache" 26 | "github.com/capitalone/checks-out/envvars" 27 | ) 28 | 29 | type Cache interface { 30 | Get(string) (interface{}, error) 31 | Set(string, interface{}) error 32 | } 33 | 34 | func Get(c context.Context, key string) (interface{}, error) { 35 | return FromContext(c).Get(key) 36 | } 37 | 38 | func Set(c context.Context, key string, value interface{}) error { 39 | return FromContext(c).Set(key, value) 40 | } 41 | 42 | // Default creates an in-memory cache with the default 43 | // 30 minute expiration period. 44 | func Default() Cache { 45 | return NewTTL(time.Minute * 30) 46 | } 47 | 48 | // NewTTL returns an in-memory cache with the specified 49 | // ttl expiration period. 50 | func NewTTL(t time.Duration) Cache { 51 | return cache.NewMemoryWithTTL(t) 52 | } 53 | 54 | var ( 55 | Longterm = cache.NewMemoryWithTTL(time.Duration(envvars.Env.Cache.LongCacheTTL)) 56 | ) 57 | -------------------------------------------------------------------------------- /cache/cache_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package cache 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/franela/goblin" 25 | "github.com/gin-gonic/gin" 26 | ) 27 | 28 | func TestCache(t *testing.T) { 29 | 30 | g := goblin.Goblin(t) 31 | g.Describe("Cache", func() { 32 | 33 | var c *gin.Context 34 | g.BeforeEach(func() { 35 | c = new(gin.Context) 36 | ToContext(c, Default()) 37 | }) 38 | 39 | g.It("Should set and get an item", func() { 40 | Set(c, "foo", "bar") 41 | v, e := Get(c, "foo") 42 | g.Assert(v).Equal("bar") 43 | g.Assert(e == nil).IsTrue() 44 | }) 45 | 46 | g.It("Should return nil when item not found", func() { 47 | v, e := Get(c, "foo") 48 | g.Assert(v == nil).IsTrue() 49 | g.Assert(e == nil).IsFalse() 50 | }) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /cache/context.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | const key = "cache" 8 | 9 | // Setter defines a context that enables setting values. 10 | type Setter interface { 11 | Set(string, interface{}) 12 | } 13 | 14 | // FromContext returns the Cache associated with this context. 15 | func FromContext(c context.Context) Cache { 16 | return c.Value(key).(Cache) 17 | } 18 | 19 | // ToContext adds the Cache to this context if it supports 20 | // the Setter interface. 21 | func ToContext(c Setter, cache Cache) { 22 | c.Set(key, cache) 23 | } 24 | -------------------------------------------------------------------------------- /docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/.gitkeep -------------------------------------------------------------------------------- /docs/categories/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Categories on checks-out 5 | http://www.capitalone.io/checks-out/categories/ 6 | Recent content in Categories on checks-out 7 | Hugo -- gohugo.io 8 | en-us 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/docs.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 1201px) { 2 | .container { 3 | width: 70%; 4 | } 5 | } 6 | @media screen and (min-width: 901px) { 7 | .container { 8 | width: 90%; 9 | } 10 | } 11 | @media screen and (max-width: 768px) { 12 | .grid-flex-cell { 13 | display: block; 14 | width:100%; 15 | } 16 | } 17 | 18 | .grid-flex-container { 19 | margin-top:30px; 20 | } 21 | .grid-flex-cell { 22 | border:none; 23 | text-align:left; 24 | } 25 | .grid-flex-cell-1of4 { 26 | min-width:250px; 27 | margin-left:30px; 28 | } 29 | h1 { 30 | font-size:32px; 31 | margin-bottom:0px; 32 | } 33 | h2 { 34 | font-size:24px; 35 | margin:60px 0px 30px 0px; 36 | } 37 | .grid-flex-cell p { 38 | margin-bottom: 30px; 39 | } 40 | 41 | .navbar { 42 | border-bottom: 1px solid #e6eaed !important; 43 | } 44 | .navbar .navbar-brand { 45 | margin-left:0px; 46 | } 47 | .side-nav a { 48 | color: #4d5659 !important; 49 | } 50 | 51 | img[alt="hook success"], 52 | img[alt="hook error"], 53 | img[alt="github registration"], 54 | img[alt="protected branches"] { 55 | border: 1px solid #e6eaed; 56 | border-radius:3px; 57 | max-width:600px; 58 | } 59 | 60 | img[alt="approval complete"], 61 | img[alt="pending approval"] { 62 | max-width:600px; 63 | } 64 | 65 | img[alt="maintainers team"] { 66 | max-width:500px; 67 | } 68 | 69 | pre code { 70 | font-family: "Roboto Mono"; 71 | font-size: 13px; 72 | } 73 | 74 | #TableOfContents { 75 | margin-top:30px; 76 | margin-bottom:-60px; 77 | } 78 | #TableOfContents li a { 79 | display:inline-block; 80 | margin-bottom:5px; 81 | } 82 | #TableOfContents li a:hover { 83 | text-decoration: underline; 84 | } 85 | section h1 { 86 | border-top: 1px solid #e6eaed; 87 | padding-top: 30px; 88 | padding-bottom:30px; 89 | margin-top: 30px; 90 | font-size:28px; 91 | } 92 | blockquote { 93 | background-color: #e3f8ff; 94 | color: #00b0e9; 95 | } 96 | blockquote p { 97 | padding:30px; 98 | font-style: normal; 99 | } 100 | blockquote a { 101 | color: #008dba; 102 | } 103 | blockquote a:hover { 104 | text-decoration: underline; 105 | } 106 | blockquote ~ h1 { 107 | border-top:0px; 108 | margin-top:-20px; 109 | } 110 | 111 | table { 112 | border: 1px solid #e6eaed !important; 113 | margin-bottom: 40px; 114 | } 115 | -------------------------------------------------------------------------------- /docs/images/app_registration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/images/app_registration.png -------------------------------------------------------------------------------- /docs/images/approval_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/images/approval_complete.png -------------------------------------------------------------------------------- /docs/images/hook_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/images/hook_error.png -------------------------------------------------------------------------------- /docs/images/hook_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/images/hook_success.png -------------------------------------------------------------------------------- /docs/images/maintainers_team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/images/maintainers_team.png -------------------------------------------------------------------------------- /docs/images/pending_approval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/images/pending_approval.png -------------------------------------------------------------------------------- /docs/images/protected_branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/docs/images/protected_branches.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | checks-out 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | http://www.capitalone.io/checks-out/overview/ 7 | 2015-12-05T16:00:21-08:00 8 | 9 | 10 | 11 | http://www.capitalone.io/checks-out/maintainers/ 12 | 2015-12-05T16:00:21-08:00 13 | 14 | 15 | 16 | http://www.capitalone.io/checks-out/branches/ 17 | 2015-12-05T16:00:21-08:00 18 | 19 | 20 | 21 | http://www.capitalone.io/checks-out/customize/ 22 | 2015-12-05T16:00:21-08:00 23 | 24 | 25 | 26 | http://www.capitalone.io/checks-out/approvals/ 27 | 2015-12-05T16:00:21-08:00 28 | 29 | 30 | 31 | http://www.capitalone.io/checks-out/install/ 32 | 2015-12-05T16:00:21-08:00 33 | 34 | 35 | 36 | http://www.capitalone.io/checks-out/api/ 37 | 2017-06-21T10:56:00-04:00 38 | 39 | 40 | 41 | http://www.capitalone.io/checks-out/support/ 42 | 2015-12-05T16:00:21-08:00 43 | 44 | 45 | 46 | http://www.capitalone.io/checks-out/faq/ 47 | 2015-12-05T16:00:21-08:00 48 | 49 | 50 | 51 | http://www.capitalone.io/checks-out/categories/ 52 | 0 53 | 54 | 55 | 56 | http://www.capitalone.io/checks-out/tags/ 57 | 0 58 | 59 | 60 | 61 | http://www.capitalone.io/checks-out/ 62 | 2015-12-05T16:00:21-08:00 63 | 0 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/tags/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tags on checks-out 5 | http://www.capitalone.io/checks-out/tags/ 6 | Recent content in Tags on checks-out 7 | Hugo -- gohugo.io 8 | en-us 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example.MAINTAINERS: -------------------------------------------------------------------------------- 1 | github-org repo-self 2 | -------------------------------------------------------------------------------- /example.checks-out: -------------------------------------------------------------------------------- 1 | approvals: 2 | [ 3 | { 4 | # count determines how many approvals are needed 5 | # self determines whether you can approve your own pull requests 6 | match: "all[count=1,self=false]" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /exterror/exterror_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package exterror 20 | 21 | import ( 22 | "errors" 23 | "strings" 24 | "testing" 25 | 26 | "github.com/mspiegel/go-multierror" 27 | ) 28 | 29 | func TestConvert(t *testing.T) { 30 | i := ExtError{Status: 404, Err: errors.New("foobar")} 31 | o := Convert(i) 32 | if o.Status != i.Status { 33 | t.Error("Expected status 404") 34 | } 35 | if o.Err.Error() != i.Err.Error() { 36 | t.Error("Incorrect error message") 37 | } 38 | } 39 | 40 | func TestAppend(t *testing.T) { 41 | prev := ExtError{Status: 404, Err: errors.New("foobar")} 42 | err := Append(prev, "baz") 43 | if prev.Status != err.(ExtError).Status { 44 | t.Error("Expected status 404") 45 | } 46 | if err.Error() != "baz. foobar" { 47 | t.Errorf("Incorrect error message: %s", err.Error()) 48 | } 49 | plain := errors.New("foobar") 50 | err = Append(plain, "baz") 51 | if err.Error() != "baz. foobar" { 52 | t.Errorf("Incorrect error message: %s", err.Error()) 53 | } 54 | var errs error 55 | e1 := Create(400, errors.New("foo")) 56 | e2 := Create(400, errors.New("bar")) 57 | e3 := Create(400, errors.New("baz")) 58 | errs = multierror.Append(errs, e1, e2, e3) 59 | errs = Append(errs, "prefix") 60 | if 400 != errs.(ExtError).Status { 61 | t.Error("Expected status 400") 62 | } 63 | if !strings.HasPrefix(errs.Error(), "prefix.") { 64 | t.Errorf("Incorrect error message: %s", errs.Error()) 65 | } 66 | } 67 | 68 | func TestConvertMultiError(t *testing.T) { 69 | e1 := Create(404, nil) 70 | e2 := Create(401, nil) 71 | e3 := Create(500, nil) 72 | out := convertMultiError(new(multierror.Error)) 73 | if out.Status != 500 { 74 | t.Error("Expected status 500") 75 | } 76 | out = convertMultiError(multierror.Append(nil, e1, e2, e3).(*multierror.Error)) 77 | if out.Status != 500 { 78 | t.Error("Expected status 500") 79 | } 80 | out = convertMultiError(multierror.Append(nil, e1, e2).(*multierror.Error)) 81 | if out.Status != 400 { 82 | t.Error("Expected status 400") 83 | } 84 | out = convertMultiError(multierror.Append(nil, e1, e2, errors.New("foobar")).(*multierror.Error)) 85 | if out.Status != 500 { 86 | t.Error("Expected status 500") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/capitalone/checks-out 2 | 3 | require ( 4 | github.com/BurntSushi/toml v0.3.0 // indirect 5 | github.com/Sirupsen/logrus v0.11.5 6 | github.com/davecgh/go-spew v1.1.0 // indirect 7 | github.com/dgrijalva/jwt-go v3.1.0+incompatible 8 | github.com/elazarl/go-bindata-assetfs v1.0.0 9 | github.com/franela/goblin v0.0.0-20170821161553-b962efd4bb2a 10 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect 11 | github.com/gin-gonic/gin v0.0.0-20170702092826-d459835d2b07 12 | github.com/go-sql-driver/mysql v1.3.0 13 | github.com/golang/protobuf v0.0.0-20170920220647-130e6b02ab05 // indirect 14 | github.com/google/go-github v0.0.0-20180723152927-e1be32f26e66 15 | github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect 16 | github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce // indirect 17 | github.com/hashicorp/go-version v0.0.0-20170914154128-fc61389e27c7 18 | github.com/hjson/hjson-go v0.2.3 19 | github.com/ianschenck/envflag v0.0.0-20140720210342-9111d830d133 20 | github.com/joho/godotenv v1.2.0 21 | github.com/koding/cache v0.0.0-20161222233015-e8a81b0b3f20 22 | github.com/kr/pretty v0.1.0 // indirect 23 | github.com/lib/pq v0.0.0-20171019223007-456514e2defe 24 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 // indirect 25 | github.com/mattn/go-isatty v0.0.3 // indirect 26 | github.com/mattn/go-jsonpointer v0.0.0-20170427002117-a39417dc2a77 27 | github.com/mattn/go-sqlite3 v1.2.0 28 | github.com/mspiegel/go-multierror v0.0.0-20170309222903-065aa648c994 29 | github.com/pelletier/go-toml v1.0.1 30 | github.com/philhofer/fwd v1.0.0 // indirect 31 | github.com/pkg/errors v0.8.0 32 | github.com/pmezard/go-difflib v1.0.0 // indirect 33 | github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20 // indirect 34 | github.com/rubenv/sql-migrate v0.0.0-20170824124545-79fe99e24311 35 | github.com/russross/meddler v0.0.0-20170319163028-9515f6b01c15 36 | github.com/stretchr/objx v0.0.0-20150928122152-1a9d0bb9f541 // indirect 37 | github.com/stretchr/testify v1.1.4 38 | github.com/tinylib/msgp v1.0.2 // indirect 39 | github.com/ugorji/go v0.0.0-20171019201919-bdcc60b419d1 // indirect 40 | github.com/ziutek/mymysql v1.5.4 // indirect 41 | golang.org/x/net v0.0.0-20171019164906-aabf50738bcd // indirect 42 | golang.org/x/oauth2 v0.0.0-20170928010508-bb50c06baba3 43 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect 44 | golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed // indirect 45 | google.golang.org/appengine v1.0.0 // indirect 46 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 47 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 48 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect 49 | gopkg.in/gorp.v1 v1.7.1 // indirect 50 | gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528 // indirect 51 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /hugo/config.toml: -------------------------------------------------------------------------------- 1 | baseurl = "http://www.capitalone.io/checks-out/" 2 | languageCode = "en-us" 3 | title = "checks-out" 4 | theme = "default" 5 | -------------------------------------------------------------------------------- /hugo/content/approvals.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-12-05T16:00:21-08:00" 3 | draft = false 4 | title = "Approval Policies" 5 | weight = 5 6 | menu = "main" 7 | toc = true 8 | +++ 9 | 10 | Here are some templates you can use to write the approval policy section 11 | of your .checks-out configuration. 12 | 13 | ```json 14 | match: "all[count=1,self=false]" 15 | ``` 16 | 17 | This policy requires one approval from anyone in the built-in 'all' 18 | group. 'all' expands to all the people in the MAINTAINERS file. 19 | Populating the MAINTAINERS file with the macro "github-org repo-self" 20 | will expand to everyone within the organization of the GitHub repository. 21 | self=false forbids the author of the pull request to approve their own 22 | request. If you want self-approval then use "all[count=1,self=true]" 23 | 24 | ```json 25 | match: "sharks[count=1,self=false] and jets[count=1,self=false]" 26 | ``` 27 | 28 | You have defined the groups 'sharks' and 'jets' in the MAINTAINERS file. 29 | This policy requires one approval from the sharks and one approval 30 | from the jets. If someone belongs to both the sharks and the jets, then 31 | their approval will count for both of the clauses and therefore they 32 | do not require a second reviewer. In general, the teams work best when 33 | they are non-overlapping. But the next example takes advantage of 34 | overlapping groups. 35 | 36 | ```json 37 | match: "all[count=2,self=false] and reviewers[count=1,self=false]" 38 | ``` 39 | 40 | This policy requires two approvals and at least one of the approvals 41 | must be from the group 'reviewers' in the MAINTAINERS file. The policy 42 | takes advantage of the fact that reviewers is a subset of all. If 43 | you only have 1 person in the reviewers group and you want the reviewer 44 | to submit pull requests and it is OK to have only a single approval 45 | against those pull request, then use 46 | "all[count=2,self=false] and reviewers[count=1,self=true]" and the 47 | reviewer must self-approve their pull request. 48 | -------------------------------------------------------------------------------- /hugo/content/branches.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-12-05T16:00:21-08:00" 3 | draft = false 4 | title = "Branches" 5 | weight = 3 6 | menu = "main" 7 | toc = true 8 | +++ 9 | 10 | # Overview 11 | 12 | Checks-out automatically enables GitHub protected branches for your repositories' __default branch__. You can further customize or disable this behavior by navigating to your repository branch settings screen in GitHub. 13 | 14 | ![protected branches](/checks-out/images/protected_branches.png) 15 | 16 | # GitHub Enterprise 17 | 18 | GitHub Enterprise 2.4 does not have an API for protected branches. As a result you will need to manually configure protected branches after enabling your repository in checks-out. This can be done by navigating to your repository branch settings screen in GitHub Enterprise (pictured above). 19 | 20 | # Other Branches 21 | 22 | Checks-out automatically enables itself for the __default__ branch. If you would like to enable checks-out for additional branches you can manually configure additional branches in your repository branch settings screen in GitHub. 23 | 24 | # Other Checks 25 | 26 | Checks-out automatically adds itself as a required status check. If you would like to enable additional status checks, such as continuous integration or code coverage, you can further customize this behavior in your repository branch settings screen in GitHub. 27 | -------------------------------------------------------------------------------- /hugo/content/maintainers.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-12-05T16:00:21-08:00" 3 | draft = false 4 | title = "Maintainers" 5 | weight = 2 6 | menu = "main" 7 | +++ 8 | 9 | The list of approvers is stored in a `MAINTAINERS` text file in the root 10 | of your repository. The maintainers file can be in either a text or HJSON 11 | format. The format is specified in the [.checks-out file](../customize) file 12 | and the default format is text. 13 | 14 | You can inspect the maintainers file for your repository using the api endpoint 15 | `/api/repos/[orgname]/[reponame]/maintainers`. The output will have any 16 | github-org and github-team macros expanded (see below). If the maintainers 17 | file cannot be parsed then an error message will be returned. 18 | 19 | # Text format 20 | 21 | The text format can be any of the following types. 22 | 23 | Username, separated by newline: 24 | 25 | ```json 26 | foo 27 | bar 28 | baz 29 | ``` 30 | 31 | Username and email address, separated by newline: 32 | 33 | ```json 34 | foo 35 | bar 36 | baz 37 | ``` 38 | 39 | FullName, email address and username, separated by newline: 40 | 41 | ```json 42 | Fooshure Jones (@foo) 43 | Bar None (@bar) 44 | Bazinga Smith (@baz) 45 | ``` 46 | 47 | Directives for importing GitHub organizations and GitHub teams. 48 | These directives populate both the 'people' and 'org' fields 49 | with the correct values (see below). 50 | 51 | ```json 52 | github-org foo # loads organization foo 53 | github-team bar # loads team bar within the organization of the repository 54 | github-team bar foo # loads team bar from organization foo 55 | github-org repo-self # loads the organization of the repository 56 | github-team repo-self # loads all the teams of the repository 57 | ``` 58 | 59 | In the examples above the groups are automatically assigned the following names: 60 | 61 | ```json 62 | foo 63 | bar 64 | foo-bar 65 | [name of organization] 66 | [names of teams] 67 | ``` 68 | 69 | # HJSON format 70 | 71 | [Human JSON](http://hjson.org) format inspired by the [Docker 72 | project](https://github.com/docker/opensource/blob/master/MAINTAINERS). 73 | Organizations can be specified using this format. 74 | 75 | The github-org and github-team directives can be used in the 'group' section 76 | but not in the 'people' section. These directives will populate the correct 77 | values into the 'people' section. 78 | 79 | ```json 80 | { 81 | people: 82 | { 83 | bob: 84 | { 85 | name: Bob Bobson 86 | email: bob@email.co 87 | } 88 | fred: 89 | { 90 | name: Fred Fredson 91 | email: fred@email.co 92 | } 93 | jon: 94 | { 95 | name: Jon Jonson 96 | email: jon@email.co 97 | } 98 | ralph: 99 | { 100 | name: Ralph Ralphington 101 | email: ralph@email.co 102 | } 103 | george: 104 | { 105 | name: George McGeorge 106 | email: george@email.co 107 | } 108 | } 109 | org: 110 | { 111 | cap: 112 | { 113 | people: [ "bob", "fred", "jon", "github-org cap" ] 114 | } 115 | iron: 116 | { 117 | people: [ "ralph", "george", "github-team iron" ] 118 | } 119 | } 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /hugo/content/overview.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-12-05T16:00:21-08:00" 3 | draft = false 4 | title = "Quick Start" 5 | weight = 1 6 | menu = "main" 7 | toc = true 8 | +++ 9 | 10 | # Overview 11 | 12 | Checks-out is a fork of [LGTM](https://github.com/lgtmco/lgtm). 13 | 14 | Checks-out will register itself as a required status check. When you enable your repository on public GitHub it is automatically configured to use [protected branches](https://github.com/blog/2051-protected-branches-and-required-status-checks). Protected branches prevent pull requests from being merged until required status checks are passing. If you are using Enterprise GitHub then you might be required to manually enable protected branches on the master branch. 15 | 16 | You can customize many options using a .checks-out file in your repository. Please see the [customization documentation](../customize) for more detail. 17 | 18 | # Setup 19 | 20 | The simplest way to configure checks-out is to create a .checks-out file the 21 | default branch of your GitHub repository with the contents: 22 | 23 | ```json 24 | approvals: 25 | [ 26 | { 27 | # count determines how many approvals are needed 28 | # self determines whether you can approve your own pull requests 29 | match: "all[count=1,self=true]" 30 | } 31 | ] 32 | ``` 33 | 34 | Create a MAINTAINERS file in the default branch with the contents: 35 | 36 | ```json 37 | github-org repo-self 38 | ``` 39 | 40 | Login to the checks-out service (this will provide the bot 41 | with credentials to monitor your repository) and enable the service 42 | for your repo. Click on the button that says "OFF" to enable the service. 43 | 44 | ## Organization Setup 45 | 46 | Create a repository named "checks-out-configuration" in your organization. 47 | The files template.checks-out and template.MAINTAINERS in the 48 | checks-out-configuration repository will be used for any repository 49 | that is missing .checks-out or MAINTAINERS files. You can also register 50 | an entire GitHub organization using the checks-out user interface. Select 51 | the "on/off" toggle next to the name of an organization. Registering 52 | an organization will register any existing repositories and will auto-register 53 | any new repositories. Added in version 0.12.0. 54 | 55 | # Approvers 56 | 57 | Define a list of individuals that can approve pull requests. This list should be store in a MAINTAINERS file to the root of your repository. Please see the [maintainers](../maintainers) documentation for supported file formats. 58 | 59 | **Please note** that checks-out pulls the MAINTAINERS file from your repository default branch (typically master). Changes to the MAINTAINERS file are not recognized until present in the default branch. Changes to the MAINTAINERS file in a pull request are therefore not recognized until merged. 60 | 61 | # Approvals 62 | 63 | Pull requests are locked and cannot be merged until the minimum number of approvals are received. Project maintainers can indicate their approval by commenting on the pull request and including "I approve" in their approval text. 64 | 65 | The service also provides integration with GitHub Reviews. An accepted GitHub 66 | Review is counted as an approval. GitHub Review that requests additional 67 | changes blocks the pull request from merging. 68 | -------------------------------------------------------------------------------- /hugo/content/support.md: -------------------------------------------------------------------------------- 1 | +++ 2 | date = "2015-12-05T16:00:21-08:00" 3 | draft = false 4 | title = "Support" 5 | weight = 7 6 | menu = "main" 7 | +++ 8 | 9 | Please be sure to read the **[faq](../faq)** before engaging the support team. This is a free service run by open source community members and we ask that you are courteous of their time. 10 | -------------------------------------------------------------------------------- /hugo/layouts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | checks-out 6 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /hugo/static/docs.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 1201px) { 2 | .container { 3 | width: 70%; 4 | } 5 | } 6 | @media screen and (min-width: 901px) { 7 | .container { 8 | width: 90%; 9 | } 10 | } 11 | @media screen and (max-width: 768px) { 12 | .grid-flex-cell { 13 | display: block; 14 | width:100%; 15 | } 16 | } 17 | 18 | .grid-flex-container { 19 | margin-top:30px; 20 | } 21 | .grid-flex-cell { 22 | border:none; 23 | text-align:left; 24 | } 25 | .grid-flex-cell-1of4 { 26 | min-width:250px; 27 | margin-left:30px; 28 | } 29 | h1 { 30 | font-size:32px; 31 | margin-bottom:0px; 32 | } 33 | h2 { 34 | font-size:24px; 35 | margin:60px 0px 30px 0px; 36 | } 37 | .grid-flex-cell p { 38 | margin-bottom: 30px; 39 | } 40 | 41 | .navbar { 42 | border-bottom: 1px solid #e6eaed !important; 43 | } 44 | .navbar .navbar-brand { 45 | margin-left:0px; 46 | } 47 | .side-nav a { 48 | color: #4d5659 !important; 49 | } 50 | 51 | img[alt="hook success"], 52 | img[alt="hook error"], 53 | img[alt="github registration"], 54 | img[alt="protected branches"] { 55 | border: 1px solid #e6eaed; 56 | border-radius:3px; 57 | max-width:600px; 58 | } 59 | 60 | img[alt="approval complete"], 61 | img[alt="pending approval"] { 62 | max-width:600px; 63 | } 64 | 65 | img[alt="maintainers team"] { 66 | max-width:500px; 67 | } 68 | 69 | pre code { 70 | font-family: "Roboto Mono"; 71 | font-size: 13px; 72 | } 73 | 74 | #TableOfContents { 75 | margin-top:30px; 76 | margin-bottom:-60px; 77 | } 78 | #TableOfContents li a { 79 | display:inline-block; 80 | margin-bottom:5px; 81 | } 82 | #TableOfContents li a:hover { 83 | text-decoration: underline; 84 | } 85 | section h1 { 86 | border-top: 1px solid #e6eaed; 87 | padding-top: 30px; 88 | padding-bottom:30px; 89 | margin-top: 30px; 90 | font-size:28px; 91 | } 92 | blockquote { 93 | background-color: #e3f8ff; 94 | color: #00b0e9; 95 | } 96 | blockquote p { 97 | padding:30px; 98 | font-style: normal; 99 | } 100 | blockquote a { 101 | color: #008dba; 102 | } 103 | blockquote a:hover { 104 | text-decoration: underline; 105 | } 106 | blockquote ~ h1 { 107 | border-top:0px; 108 | margin-top:-20px; 109 | } 110 | 111 | table { 112 | border: 1px solid #e6eaed !important; 113 | margin-bottom: 40px; 114 | } 115 | -------------------------------------------------------------------------------- /hugo/static/images/app_registration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/static/images/app_registration.png -------------------------------------------------------------------------------- /hugo/static/images/approval_complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/static/images/approval_complete.png -------------------------------------------------------------------------------- /hugo/static/images/hook_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/static/images/hook_error.png -------------------------------------------------------------------------------- /hugo/static/images/hook_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/static/images/hook_success.png -------------------------------------------------------------------------------- /hugo/static/images/maintainers_team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/static/images/maintainers_team.png -------------------------------------------------------------------------------- /hugo/static/images/pending_approval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/static/images/pending_approval.png -------------------------------------------------------------------------------- /hugo/static/images/protected_branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/static/images/protected_branches.png -------------------------------------------------------------------------------- /hugo/themes/default/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ .Title }} · {{ .Site.Title }} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 |
29 | 30 |
31 |

{{ .Title }}

32 | {{ if .Params.toc }} {{ .TableOfContents }} {{ end }} 33 |
{{ .Content }}
34 |
35 | 36 |
37 | {{ $currentPage := . }} 38 | 47 |
48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /hugo/themes/default/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/hugo/themes/default/static/.gitkeep -------------------------------------------------------------------------------- /logstats/logstats.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package logstats 20 | 21 | import ( 22 | "sync" 23 | "time" 24 | 25 | "github.com/capitalone/checks-out/envvars" 26 | "github.com/capitalone/checks-out/model" 27 | "github.com/capitalone/checks-out/set" 28 | "github.com/capitalone/checks-out/store/datastore" 29 | 30 | log "github.com/Sirupsen/logrus" 31 | ) 32 | 33 | var ( 34 | lock = sync.Mutex{} 35 | pr = set.Empty() 36 | approvers = set.Empty() 37 | disapprovers = set.Empty() 38 | ) 39 | 40 | func RecordPR(id string) { 41 | lock.Lock() 42 | pr.Add(id) 43 | lock.Unlock() 44 | } 45 | 46 | func RecordApprover(id string) { 47 | lock.Lock() 48 | approvers.Add(id) 49 | lock.Unlock() 50 | } 51 | 52 | func RecordDisapprover(id string) { 53 | lock.Lock() 54 | disapprovers.Add(id) 55 | lock.Unlock() 56 | } 57 | 58 | func resetStats() { 59 | pr = set.Empty() 60 | approvers = set.Empty() 61 | disapprovers = set.Empty() 62 | } 63 | 64 | func usersAndOrgs(repos []*model.Repo) (set.Set, set.Set) { 65 | users := set.Empty() 66 | orgs := set.Empty() 67 | for _, repo := range repos { 68 | if repo.Org { 69 | orgs.Add(repo.Owner) 70 | } else { 71 | users.Add(repo.Owner) 72 | } 73 | } 74 | return users, orgs 75 | } 76 | 77 | func writeLog() { 78 | repos, err := datastore.Get().GetAllRepos() 79 | if err != nil { 80 | log.Error("Periodic logging unable to fetch repository list", err) 81 | } else { 82 | users, orgs := usersAndOrgs(repos) 83 | log.Infof("Monitoring %d repositories", len(repos)) 84 | log.Infof("Monitoring %d users", len(users)) 85 | log.Infof("Monitoring %d organizations", len(orgs)) 86 | } 87 | log.Infof("Accepted %d pull requests in last period", len(pr)) 88 | log.Infof("Accepted %d approvers in last period", len(approvers)) 89 | log.Infof("Accepted %d disapprovers in last period", len(disapprovers)) 90 | } 91 | 92 | func logTask() { 93 | period := envvars.Env.Monitor.LogPeriod 94 | if period == 0 { 95 | return 96 | } 97 | t := time.NewTicker(period) 98 | for { 99 | lock.Lock() 100 | writeLog() 101 | resetStats() 102 | lock.Unlock() 103 | <-t.C 104 | } 105 | } 106 | 107 | func Start() { 108 | go logTask() 109 | } 110 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package main 20 | 21 | import ( 22 | "flag" 23 | "fmt" 24 | "net/http" 25 | "time" 26 | 27 | "github.com/capitalone/checks-out/envvars" 28 | "github.com/capitalone/checks-out/logstats" 29 | "github.com/capitalone/checks-out/migration" 30 | _ "github.com/capitalone/checks-out/notifier/github" 31 | _ "github.com/capitalone/checks-out/notifier/slack" 32 | "github.com/capitalone/checks-out/remote" 33 | "github.com/capitalone/checks-out/router" 34 | "github.com/capitalone/checks-out/store/datastore" 35 | "github.com/capitalone/checks-out/usage" 36 | "github.com/capitalone/checks-out/version" 37 | 38 | "github.com/Sirupsen/logrus" 39 | _ "github.com/joho/godotenv/autoload" 40 | ) 41 | 42 | func setLogLevel(level string) { 43 | switch level { 44 | case "panic": 45 | logrus.SetLevel(logrus.PanicLevel) 46 | case "fatal": 47 | logrus.SetLevel(logrus.FatalLevel) 48 | case "error": 49 | logrus.SetLevel(logrus.ErrorLevel) 50 | case "warn": 51 | logrus.SetLevel(logrus.WarnLevel) 52 | case "info": 53 | logrus.SetLevel(logrus.InfoLevel) 54 | case "debug": 55 | logrus.SetLevel(logrus.DebugLevel) 56 | default: 57 | logrus.Fatal("Unrecognized log level ", level) 58 | } 59 | } 60 | 61 | func startService() { 62 | 63 | err := envvars.Validate() 64 | if err != nil { 65 | logrus.Fatal(err) 66 | } 67 | 68 | setLogLevel(envvars.Env.Monitor.LogLevel) 69 | 70 | logstats.Start() 71 | usage.Start() 72 | 73 | r := remote.Get() 74 | ds := datastore.Get() 75 | 76 | err = migration.Migrate(r, ds) 77 | 78 | if err != nil { 79 | logrus.Fatal(err) 80 | } 81 | 82 | handler := router.Load() 83 | 84 | logrus.Infof("Starting %s service on %s", envvars.Env.Branding.ShortName, time.Now().Format(time.RFC1123)) 85 | 86 | if envvars.Env.Server.Cert != "" { 87 | logrus.Fatal( 88 | http.ListenAndServeTLS(envvars.Env.Server.Addr, envvars.Env.Server.Cert, envvars.Env.Server.Key, handler), 89 | ) 90 | } else { 91 | logrus.Fatal( 92 | http.ListenAndServe(envvars.Env.Server.Addr, handler), 93 | ) 94 | } 95 | 96 | } 97 | 98 | func main() { 99 | ver := flag.Bool("version", false, "print version") 100 | env := flag.Bool("env", false, "print environment variables") 101 | help := flag.Bool("help", false, "print help information") 102 | flag.Parse() 103 | if *help { 104 | flag.PrintDefaults() 105 | } else if *ver { 106 | fmt.Println(version.Version) 107 | } else if *env { 108 | envvars.Usage() 109 | } else { 110 | startService() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /matcher/language_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package matcher 20 | 21 | import ( 22 | "github.com/stretchr/testify/assert" 23 | "testing" 24 | ) 25 | 26 | func TestBuildParseTree(t *testing.T) { 27 | input := "a and b or (us and them) or anyone and not d or f[self=true,count=10] and nof(a,b,c[self=false,count=2] and (d or e),1)" 28 | tokens := BuildTokens(input) 29 | _, err := BuildParseTree(tokens) 30 | assert.Nil(t, err) 31 | } 32 | 33 | func TestBuildParseTreeSmall(t *testing.T) { 34 | input := "a and b or c" 35 | tokens := BuildTokens(input) 36 | _, err := BuildParseTree(tokens) 37 | assert.Nil(t, err) 38 | } 39 | 40 | func TestBuildParseTreeParens(t *testing.T) { 41 | input := "(a and b) or not (c and (d or e))" 42 | tokens := BuildTokens(input) 43 | _, err := BuildParseTree(tokens) 44 | assert.Nil(t, err) 45 | } 46 | 47 | func TestBuildParseTreeInvalid(t *testing.T) { 48 | vals := []string{ 49 | "(a and b) or not (c and (d or e)))", 50 | ")", 51 | "(a and )", 52 | "( and a)", 53 | } 54 | for _, input := range vals { 55 | tokens := BuildTokens(input) 56 | _, err := BuildParseTree(tokens) 57 | assert.NotNil(t, err) 58 | } 59 | } 60 | 61 | func TestInvalidErrorMessages(t *testing.T) { 62 | tokens := BuildTokens("foo[and]") 63 | _, err := BuildParseTree(tokens) 64 | assert.Equal(t, err.Error(), "invalid 'and' at position 5") 65 | tokens = BuildTokens("foo[a=1") 66 | _, err = BuildParseTree(tokens) 67 | assert.Equal(t, err.Error(), "missing ']' at position 7") 68 | tokens = BuildTokens("foo[a,b,c]") 69 | _, err = BuildParseTree(tokens) 70 | assert.Equal(t, err.Error(), "invalid ',' at position 6") 71 | } 72 | -------------------------------------------------------------------------------- /matcher/scanner.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package matcher 20 | 21 | /* 22 | language parse tokens: 23 | 24 | NAME - refers to: a person_name, an org_name, a special org name (us, them, any), an attribute name, an attribute value, a number, true, or false 25 | LBRACKET - [ for specifying attributes 26 | RBRACKET - ] for specifying attributes 27 | LPAREN - ( for grouping terms 28 | RPAREN - ) for grouping terms 29 | EQUAL - = for assigning attribute value to an attribute name 30 | COMMA - , for separating items in attribute or parameter lists 31 | AND - logical and 32 | OR - logical or 33 | NOT - logical not 34 | LBRACE - { for specifying anonymous groups 35 | RBRACE - } for specifying anonymous groups 36 | */ 37 | 38 | type token struct { 39 | name string 40 | value tokenType 41 | pos int 42 | } 43 | 44 | type tokenType int 45 | 46 | const ( 47 | TOKEN_INVALID tokenType = iota 48 | TOKEN_NAME 49 | TOKEN_LBRACKET 50 | TOKEN_RBRACKET 51 | TOKEN_EQUAL 52 | TOKEN_LPAREN 53 | TOKEN_RPAREN 54 | TOKEN_COMMA 55 | TOKEN_AND 56 | TOKEN_OR 57 | TOKEN_NOT 58 | TOKEN_LBRACE 59 | TOKEN_RBRACE 60 | ) 61 | 62 | func getTokenType(curString string) tokenType { 63 | switch curString { 64 | case "and": 65 | return TOKEN_AND 66 | case "or": 67 | return TOKEN_OR 68 | case "not": 69 | return TOKEN_NOT 70 | default: 71 | return TOKEN_NAME 72 | } 73 | } 74 | 75 | func BuildTokens(in string) []token { 76 | var curString []rune 77 | var curPos int 78 | out := []token{} 79 | f := func(t token) { 80 | if len(curString) > 0 { 81 | cur := string(curString) 82 | out = append(out, token{cur, getTokenType(cur), curPos}) 83 | curString = curString[:0] 84 | curPos = 0 85 | } 86 | if len(t.name) > 0 { 87 | out = append(out, t) 88 | } 89 | } 90 | for i, v := range in { 91 | pos := i + 1 92 | switch v { 93 | case '[': 94 | f(token{"[", TOKEN_LBRACKET, pos}) 95 | case ']': 96 | f(token{"]", TOKEN_RBRACKET, pos}) 97 | case '=': 98 | f(token{"=", TOKEN_EQUAL, pos}) 99 | case '(': 100 | f(token{"(", TOKEN_LPAREN, pos}) 101 | case ')': 102 | f(token{")", TOKEN_RPAREN, pos}) 103 | case '{': 104 | f(token{"{", TOKEN_LBRACE, pos}) 105 | case '}': 106 | f(token{"}", TOKEN_RBRACE, pos}) 107 | case ',': 108 | f(token{",", TOKEN_COMMA, pos}) 109 | case ' ', '\n', '\t', '\r': 110 | f(token{"", 0, 0}) 111 | default: 112 | if curPos == 0 { 113 | curPos = pos 114 | } 115 | curString = append(curString, v) 116 | } 117 | } 118 | f(token{"", 0, 0}) 119 | return out 120 | } 121 | -------------------------------------------------------------------------------- /migration/migration.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package migration 20 | 21 | import ( 22 | "context" 23 | 24 | "github.com/capitalone/checks-out/envvars" 25 | "github.com/capitalone/checks-out/remote" 26 | "github.com/capitalone/checks-out/store" 27 | 28 | log "github.com/Sirupsen/logrus" 29 | "github.com/mspiegel/go-multierror" 30 | ) 31 | 32 | // Migrate performs any (store x remote) operations necessary 33 | // after a database migration. 34 | func Migrate(r remote.Remote, s store.Store) error { 35 | var errs error 36 | ids := s.GetMigrations() 37 | if ids.Contains("002_org.sql") { 38 | errs = multierror.Append(errs, insertRepoOrgs(r, s)) 39 | } 40 | if ids.Contains("005_oauth_scope.sql") { 41 | errs = multierror.Append(errs, insertDefaultScope(s)) 42 | } 43 | return errs 44 | } 45 | 46 | func insertDefaultScope(s store.Store) error { 47 | log.Info("Applying 005_oauth_scope.sql migrations") 48 | count := 0 49 | users, err := s.GetAllUsers() 50 | if err != nil { 51 | return err 52 | } 53 | for _, user := range users { 54 | user.Scopes = envvars.Env.Github.Scope 55 | err = s.UpdateUser(user) 56 | if err != nil { 57 | log.Warnf("Unable to update user %s: %s", user.Login, err) 58 | continue 59 | } 60 | count++ 61 | } 62 | log.Infof("Applied 005_oauth_scope.sql migrations to %d out of a total %d users", count, len(users)) 63 | return nil 64 | } 65 | 66 | func insertRepoOrgs(r remote.Remote, s store.Store) error { 67 | log.Info("Applying 002_org.sql migrations") 68 | count := 0 69 | repos, err := s.GetAllRepos() 70 | if err != nil { 71 | return err 72 | } 73 | for _, repo := range repos { 74 | model, err := s.GetUser(repo.UserID) 75 | if err != nil { 76 | log.Warnf("Unable to retrieve user %s (id %d) for repo %s: %s", 77 | repo.Owner, repo.UserID, repo.Slug, err) 78 | continue 79 | } 80 | update, err := r.GetRepo(context.Background(), model, repo.Owner, repo.Name) 81 | if err != nil { 82 | log.Warnf("Unable to retrieve repo %s: %s", repo.Slug, err) 83 | continue 84 | } 85 | repo.Org = update.Org 86 | err = s.UpdateRepo(repo) 87 | if err != nil { 88 | log.Warnf("Unable to update repo %s: %s", repo.Slug, err) 89 | continue 90 | } 91 | count++ 92 | } 93 | log.Infof("Applied 002_org.sql migrations to %d out of a total %d repos", count, len(repos)) 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /model/approval_op.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | type ApprovalOp int 22 | 23 | const ( 24 | Approval ApprovalOp = iota 25 | DisapprovalInsert 26 | DisapprovalRemove 27 | ValidAuthor 28 | ValidTitle 29 | ) 30 | -------------------------------------------------------------------------------- /model/audit.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/set" 23 | ) 24 | 25 | type AuditConfig struct { 26 | // enable auditing of commits. Default is false. 27 | Enable bool `json:"enable"` 28 | // which branches to audit 29 | Branches set.Set `json:"branches"` 30 | } 31 | 32 | func DefaultAudit() AuditConfig { 33 | branches := set.New("master") 34 | return AuditConfig{ 35 | Enable: false, 36 | Branches: branches, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /model/branch.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | type PullRequest struct { 22 | Issue 23 | Branch Branch 24 | Body string 25 | } 26 | 27 | type Branch struct { 28 | CompareName string 29 | CompareSHA string 30 | CompareOwner string 31 | Mergeable bool 32 | Merged bool 33 | MergeCommitSHA string 34 | BaseName string 35 | BaseSHA string 36 | } 37 | 38 | type BranchCompare struct { 39 | Status string 40 | AheadBy int 41 | BehindBy int 42 | TotalCommits int 43 | } 44 | -------------------------------------------------------------------------------- /model/capabilities.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "errors" 23 | 24 | "github.com/capitalone/checks-out/set" 25 | 26 | "github.com/mspiegel/go-multierror" 27 | ) 28 | 29 | type Capabilities struct { 30 | Org struct { 31 | Read bool 32 | } 33 | Repo struct { 34 | Tag bool 35 | Merge bool 36 | DeleteBranch bool 37 | CommitStatus bool 38 | PRWriteComment bool 39 | DeploymentStatus bool 40 | } 41 | } 42 | 43 | func AllowAll() *Capabilities { 44 | caps := new(Capabilities) 45 | caps.Org.Read = true 46 | caps.Repo.Tag = true 47 | caps.Repo.Merge = true 48 | caps.Repo.DeleteBranch = true 49 | caps.Repo.CommitStatus = true 50 | caps.Repo.PRWriteComment = true 51 | caps.Repo.DeploymentStatus = true 52 | return caps 53 | } 54 | 55 | func validateCapabilities(c *Config, caps *Capabilities) error { 56 | var errs error 57 | errMsgs := set.Empty() 58 | if !caps.Repo.CommitStatus { 59 | errMsgs.Add("commit status OAuth scope is required") 60 | } 61 | if c.Tag.Enable && !caps.Repo.Tag { 62 | errMsgs.Add("unable to git tag with provided OAuth scopes") 63 | } 64 | if c.Merge.Enable && !caps.Repo.Merge { 65 | errMsgs.Add("unable to git merge with provided OAuth scopes") 66 | } 67 | if c.Merge.Enable && c.Merge.Delete && !caps.Repo.DeleteBranch { 68 | errMsgs.Add("unable to delete branch with provided OAuth scopes") 69 | } 70 | for _, policy := range c.Approvals { 71 | if policy.Tag != nil && policy.Tag.Enable && !caps.Repo.Tag { 72 | errMsgs.Add("unable to git tag with provided OAuth scopes") 73 | } 74 | if policy.Merge != nil && policy.Merge.Enable && !caps.Repo.Merge { 75 | errMsgs.Add("unable to git merge with provided OAuth scopes") 76 | } 77 | if policy.Merge != nil && policy.Merge.Enable && policy.Merge.Delete && !caps.Repo.DeleteBranch { 78 | errMsgs.Add("unable to delete branch with provided OAuth scopes") 79 | } 80 | } 81 | if c.Comment.Enable { 82 | for _, target := range c.Comment.Targets { 83 | if target.Target == Github.String() && !caps.Repo.PRWriteComment { 84 | errMsgs.Add("unable to add PR comment with provided OAuth scopes") 85 | } 86 | } 87 | } 88 | for msg := range errMsgs { 89 | errs = multierror.Append(errs, errors.New(msg)) 90 | } 91 | return errs 92 | } 93 | -------------------------------------------------------------------------------- /model/comment.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "fmt" 23 | "regexp" 24 | "time" 25 | 26 | "github.com/capitalone/checks-out/envvars" 27 | "github.com/capitalone/checks-out/strings/lowercase" 28 | ) 29 | 30 | var CommentPrefix = fmt.Sprintf("Message from %s --", envvars.Env.Branding.ShortName) 31 | 32 | type Comment struct { 33 | Author lowercase.String 34 | Body string 35 | SubmittedAt time.Time 36 | } 37 | 38 | // IsApproval returns true if the comment body matches the regular 39 | // expression pattern. 40 | func (c *Comment) IsApproval(req *ApprovalRequest) bool { 41 | var regExp *regexp.Regexp 42 | policy := FindApprovalPolicy(req) 43 | if policy.Pattern != nil { 44 | regExp = policy.Pattern.Regex 45 | } else { 46 | regExp = req.Config.Pattern.Regex 47 | } 48 | if regExp == nil { 49 | // this should never happen 50 | return false 51 | } 52 | return regExp.MatchString(c.Body) 53 | } 54 | 55 | // IsDisapproval returns true if the comment body matches the 56 | // antipattern regular expression. 57 | func (c *Comment) IsDisapproval(req *ApprovalRequest) bool { 58 | var regExp *regexp.Regexp 59 | policy := FindApprovalPolicy(req) 60 | if policy.AntiPattern != nil { 61 | regExp = policy.AntiPattern.Regex 62 | } else if req.Config.AntiPattern != nil { 63 | regExp = req.Config.AntiPattern.Regex 64 | } 65 | if regExp == nil { 66 | // disapproval matching is optional 67 | return false 68 | } 69 | return regExp.MatchString(c.Body) 70 | } 71 | 72 | func (c *Comment) GetAuthor() lowercase.String { 73 | return c.Author 74 | } 75 | 76 | func (c *Comment) GetBody() string { 77 | return c.Body 78 | } 79 | 80 | func (c *Comment) GetSubmittedAt() time.Time { 81 | return c.SubmittedAt 82 | } 83 | 84 | func (c *Comment) String() string { 85 | if c == nil { 86 | return "nil" 87 | } 88 | return fmt.Sprintf("{Author: %s, Comment: %s}", c.Author, c.Body) 89 | } 90 | -------------------------------------------------------------------------------- /model/commit.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/capitalone/checks-out/strings/lowercase" 4 | 5 | type Commit struct { 6 | Author lowercase.String 7 | Committer string 8 | Message string 9 | SHA string 10 | Parents []string 11 | } 12 | 13 | func DefaultCommit() CommitConfig { 14 | return CommitConfig{ 15 | Range: Head, 16 | AntiRange: Head, 17 | TagRange: Head, 18 | IgnoreUIMerge: true, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /model/commitfile.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | type CommitFile struct { 22 | Filename string 23 | } 24 | -------------------------------------------------------------------------------- /model/commitrange.go: -------------------------------------------------------------------------------- 1 | //go:generate enumer -type=CommitRange 2 | /* 3 | 4 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 5 | SPDX-License-Identifier: Apache-2.0 6 | Copyright 2017 Capital One Services, LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and limitations under the License. 18 | 19 | */ 20 | package model 21 | 22 | type CommitRange int 23 | 24 | const ( 25 | Head CommitRange = iota 26 | All 27 | ) 28 | -------------------------------------------------------------------------------- /model/commitrange_enumer.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=CommitRange"; DO NOT EDIT 2 | /* 3 | 4 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 5 | SPDX-License-Identifier: Apache-2.0 6 | Copyright 2017 Capital One Services, LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and limitations under the License. 18 | 19 | */ 20 | package model 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | ) 26 | 27 | // CommitRange is an enum. 28 | // It is serialized as a string in JSON and as an integer in SQL. 29 | 30 | // CommitRange enum maps. 31 | var ( 32 | strMapCommitRange = map[string]CommitRange{ 33 | "all": All, 34 | "head": Head, 35 | } 36 | 37 | intMapCommitRange = map[CommitRange]string{ 38 | All: "all", 39 | Head: "head", 40 | } 41 | ) 42 | 43 | // Known says whether or not this value is a known enum value. 44 | func (s CommitRange) Known() bool { 45 | _, ok := intMapCommitRange[s] 46 | return ok 47 | } 48 | 49 | // String is for the standard stringer interface. 50 | func (s CommitRange) String() string { 51 | return intMapCommitRange[s] 52 | } 53 | 54 | // UnmarshalJSON satisfies the json.Unmarshaler 55 | func (s *CommitRange) UnmarshalJSON(data []byte) error { 56 | str := "" 57 | err := json.Unmarshal(data, &str) 58 | if err != nil { 59 | return err 60 | } 61 | var ok bool 62 | *s, ok = strMapCommitRange[str] 63 | if !ok { 64 | return fmt.Errorf("Unknown CommitRange enum value: %s", str) 65 | } 66 | return nil 67 | } 68 | 69 | // MarshalJSON satisfies the json.Marshaler 70 | func (s CommitRange) MarshalJSON() ([]byte, error) { 71 | if !s.Known() { 72 | return nil, fmt.Errorf("Unknown CommitRange enum value: %d", int(s)) 73 | } 74 | name := intMapCommitRange[s] 75 | return json.Marshal(name) 76 | } 77 | -------------------------------------------------------------------------------- /model/commitstatus.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/capitalone/checks-out/envvars" 25 | ) 26 | 27 | // ServiceName is the status context posted to remote 28 | var ServiceName = fmt.Sprintf("approvals/%s", envvars.Env.Branding.Name) 29 | 30 | // AuditName is the prefix of the audit context posted to remote 31 | var AuditName = fmt.Sprintf("audit/%s", envvars.Env.Branding.Name) 32 | 33 | type CombinedStatus struct { 34 | State string 35 | Statuses map[string]CommitStatus 36 | } 37 | 38 | type CommitStatus struct { 39 | Context string 40 | State string 41 | Description string 42 | } 43 | -------------------------------------------------------------------------------- /model/deployment.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "errors" 23 | 24 | "github.com/capitalone/checks-out/hjson" 25 | ) 26 | 27 | type DeploymentConfigs map[string]DeploymentConfig 28 | 29 | type DeploymentConfig struct { 30 | Tasks []string `json:"tasks"` 31 | Environment *string `json:"env"` 32 | } 33 | 34 | type DeploymentInfo struct { 35 | Ref string 36 | Task string 37 | Environment string 38 | } 39 | 40 | func (c *Config) LoadDeploymentMap(deployData []byte) error { 41 | if len(deployData) == 0 { 42 | return errors.New("No content in deployment map") 43 | } 44 | err := hjson.Unmarshal(deployData, &c.Deployment.DeploymentMap) 45 | return err 46 | } 47 | -------------------------------------------------------------------------------- /model/deployment_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "testing" 23 | ) 24 | 25 | var deployFile = ` 26 | { 27 | deploy: 28 | { 29 | tasks: [ "t1", "t2", "t3" ] 30 | env: qa 31 | } 32 | master: 33 | { 34 | tasks: [ "t1" ] 35 | env: prod 36 | } 37 | preprod: 38 | { 39 | env: preprod 40 | } 41 | preprod2: 42 | { 43 | tasks: [ "t5" ] 44 | } 45 | } 46 | ` 47 | 48 | func TestDeploymentHJSON(t *testing.T) { 49 | c := DefaultConfig() 50 | err := c.LoadDeploymentMap([]byte(deployFile)) 51 | d := c.Deployment.DeploymentMap 52 | if err != nil { 53 | panic(err) 54 | } 55 | if len(d) != 4 { 56 | t.Fatalf("Should have 4 entries in d, only have %d", len(d)) 57 | } 58 | deploy, ok := d["deploy"] 59 | if !ok { 60 | t.Fatalf("should have an entry for deploy") 61 | } 62 | if *deploy.Environment != "qa" { 63 | t.Fatalf("should have qa for environment, had %s", *deploy.Environment) 64 | } 65 | if len(deploy.Tasks) != 3 { 66 | t.Fatalf("should have had 3 entries in tasks, had %d", len(deploy.Tasks)) 67 | } 68 | 69 | master, ok := d["master"] 70 | if !ok { 71 | t.Fatalf("should have an entry for master") 72 | } 73 | if *master.Environment != "prod" { 74 | t.Fatalf("should have prod for environment, had %s", *master.Environment) 75 | } 76 | if len(master.Tasks) != 1 { 77 | t.Fatalf("should have had 1 entry in tasks, had %d", len(master.Tasks)) 78 | } 79 | 80 | preprod, ok := d["preprod"] 81 | if !ok { 82 | t.Fatalf("should have an entry for preprod") 83 | } 84 | if *preprod.Environment != "preprod" { 85 | t.Fatalf("should have prod for environment, had %s", *preprod.Environment) 86 | } 87 | if len(preprod.Tasks) != 0 { 88 | t.Fatalf("should have had 0 entries in tasks, had %d", len(preprod.Tasks)) 89 | } 90 | 91 | preprod2, ok := d["preprod2"] 92 | if !ok { 93 | t.Fatalf("should have an entry for preprod2") 94 | } 95 | if preprod2.Environment != nil { 96 | t.Fatalf("should have no environment, had %s", *preprod2.Environment) 97 | } 98 | if len(preprod2.Tasks) != 1 { 99 | t.Fatalf("should have had 1 entry in tasks, had %d", len(preprod2.Tasks)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /model/feedback.go: -------------------------------------------------------------------------------- 1 | //go:generate enumer -type=FeedbackType 2 | /* 3 | 4 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 5 | SPDX-License-Identifier: Apache-2.0 6 | Copyright 2017 Capital One Services, LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and limitations under the License. 18 | 19 | */ 20 | package model 21 | 22 | import "encoding/json" 23 | 24 | func DefaultFeedback() FeedbackConfig { 25 | return FeedbackConfig{ 26 | Types: []FeedbackType{CommentType, ReviewType}, 27 | AuthorAffirm: true, 28 | } 29 | } 30 | 31 | // Used to avoid recursion in UnmarshalJSON 32 | type shadowFeedbackConfig FeedbackConfig 33 | 34 | func (c *FeedbackConfig) UnmarshalJSON(text []byte) error { 35 | dummy := shadowFeedbackConfig(DefaultFeedback()) 36 | err := json.Unmarshal(text, &dummy) 37 | if err != nil { 38 | return err 39 | } 40 | *c = FeedbackConfig(dummy) 41 | return nil 42 | } 43 | 44 | type FeedbackType int 45 | 46 | const ( 47 | CommentType FeedbackType = iota 48 | ReviewType 49 | ) 50 | -------------------------------------------------------------------------------- /model/feedback_enumer.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=FeedbackType"; DO NOT EDIT 2 | /* 3 | 4 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 5 | SPDX-License-Identifier: Apache-2.0 6 | Copyright 2017 Capital One Services, LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and limitations under the License. 18 | 19 | */ 20 | package model 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | ) 26 | 27 | // FeedbackType is an enum. 28 | // It is serialized as a string in JSON and as an integer in SQL. 29 | 30 | // FeedbackType enum maps. 31 | var ( 32 | strMapFeedbackType = map[string]FeedbackType{ 33 | "comment": CommentType, 34 | "review": ReviewType, 35 | } 36 | 37 | intMapFeedbackType = map[FeedbackType]string{ 38 | CommentType: "comment", 39 | ReviewType: "review", 40 | } 41 | ) 42 | 43 | // Known says whether or not this value is a known enum value. 44 | func (s FeedbackType) Known() bool { 45 | _, ok := intMapFeedbackType[s] 46 | return ok 47 | } 48 | 49 | // String is for the standard stringer interface. 50 | func (s FeedbackType) String() string { 51 | return intMapFeedbackType[s] 52 | } 53 | 54 | // UnmarshalJSON satisfies the json.Unmarshaler 55 | func (s *FeedbackType) UnmarshalJSON(data []byte) error { 56 | str := "" 57 | err := json.Unmarshal(data, &str) 58 | if err != nil { 59 | return err 60 | } 61 | var ok bool 62 | *s, ok = strMapFeedbackType[str] 63 | if !ok { 64 | return fmt.Errorf("Unknown FeedbackType enum value: %s", str) 65 | } 66 | return nil 67 | } 68 | 69 | // MarshalJSON satisfies the json.Marshaler 70 | func (s FeedbackType) MarshalJSON() ([]byte, error) { 71 | if !s.Known() { 72 | return nil, fmt.Errorf("Unknown FeedbackType enum value: %d", int(s)) 73 | } 74 | name := intMapFeedbackType[s] 75 | return json.Marshal(name) 76 | } 77 | -------------------------------------------------------------------------------- /model/issue.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/strings/lowercase" 23 | ) 24 | 25 | type Issue struct { 26 | Number int 27 | Title string 28 | Author lowercase.String 29 | } 30 | -------------------------------------------------------------------------------- /model/maintainer.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/capitalone/checks-out/set" 25 | ) 26 | 27 | // Person represets an individual in the MAINTAINERS file. 28 | type Person struct { 29 | Name string `json:"name" toml:"name"` 30 | Email string `json:"email" toml:"email"` 31 | Login string `json:"login" toml:"login"` 32 | } 33 | 34 | // Org represents a group, team or subset of users. 35 | type Org interface { 36 | GetPeople() (set.Set, error) 37 | } 38 | 39 | type OrgSerde struct { 40 | People set.Set `json:"people" toml:"people"` 41 | } 42 | 43 | // Maintainer represents a MAINTAINERS file. 44 | type Maintainer struct { 45 | RawPeople map[string]*Person `json:"people" toml:"people"` 46 | RawOrg map[string]*OrgSerde `json:"org" toml:"org"` 47 | } 48 | 49 | var MaintTypes = set.New("text", "hjson", "toml", "legacy") 50 | 51 | func validateMaintainerConfig(c *MaintainersConfig) error { 52 | if !MaintTypes.Contains(c.Type) { 53 | return fmt.Errorf("%s is not one of the permitted MAINTAINER types %s", 54 | c.Type, MaintTypes.Keys()) 55 | } 56 | return nil 57 | } 58 | 59 | func (o *OrgSerde) GetPeople() (set.Set, error) { 60 | return o.People, nil 61 | } 62 | -------------------------------------------------------------------------------- /model/merge.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import "encoding/json" 22 | 23 | func DefaultMerge() MergeConfig { 24 | return MergeConfig{ 25 | Enable: false, 26 | UpToDate: true, 27 | Method: "merge", 28 | Delete: false, 29 | } 30 | } 31 | 32 | // Used to avoid recursion in UnmarshalJSON 33 | type shadowMergeConfig MergeConfig 34 | 35 | func (m *MergeConfig) UnmarshalJSON(text []byte) error { 36 | dummy := shadowMergeConfig(DefaultMerge()) 37 | err := json.Unmarshal(text, &dummy) 38 | if err != nil { 39 | return err 40 | } 41 | *m = MergeConfig(dummy) 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /model/org.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | type GitHubOrg struct { 22 | Login string `json:"login"` 23 | Avatar string `json:"avatar"` 24 | Enabled bool `json:"enabled"` 25 | Admin bool `json:"admin"` 26 | } 27 | -------------------------------------------------------------------------------- /model/repo.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | type Repo struct { 22 | ID int64 `json:"id,omitempty" meddler:"repo_id,pk"` 23 | UserID int64 `json:"-" meddler:"repo_user_id"` 24 | Owner string `json:"owner" meddler:"repo_owner"` 25 | Name string `json:"name" meddler:"repo_name"` 26 | Slug string `json:"slug" meddler:"repo_slug"` 27 | Link string `json:"link_url" meddler:"repo_link"` 28 | Private bool `json:"private" meddler:"repo_private"` 29 | Secret string `json:"-" meddler:"repo_secret"` 30 | Org bool `json:"org" meddler:"repo_org"` 31 | } 32 | 33 | type Perm struct { 34 | Pull bool 35 | Push bool 36 | Admin bool 37 | } 38 | 39 | 40 | type OrgDb struct { 41 | ID int64 `json:"id,omitempty" meddler:"org_id,pk"` 42 | UserID int64 `json:"-" meddler:"org_user_id"` 43 | Owner string `json:"owner" meddler:"org_owner"` 44 | Link string `json:"link_url" meddler:"org_link"` 45 | Private bool `json:"private" meddler:"org_private"` 46 | Secret string `json:"-" meddler:"org_secret"` 47 | } 48 | -------------------------------------------------------------------------------- /model/review.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "time" 23 | 24 | "github.com/capitalone/checks-out/strings/lowercase" 25 | ) 26 | 27 | type Review struct { 28 | ID int64 29 | Author lowercase.String 30 | Body string 31 | SubmittedAt time.Time 32 | State lowercase.String 33 | } 34 | 35 | // IsApproval returns true if the review has been approved 36 | func (r *Review) IsApproval(req *ApprovalRequest) bool { 37 | return r.State.String() == "approved" 38 | } 39 | 40 | // IsDisapproval returns true if changes have been requested 41 | func (r *Review) IsDisapproval(req *ApprovalRequest) bool { 42 | return r.State.String() == "changes_requested" 43 | } 44 | 45 | func (r *Review) GetAuthor() lowercase.String { 46 | return r.Author 47 | } 48 | 49 | func (r *Review) GetBody() string { 50 | return r.Body 51 | } 52 | 53 | func (r *Review) GetSubmittedAt() time.Time { 54 | return r.SubmittedAt 55 | } 56 | -------------------------------------------------------------------------------- /model/semver.go: -------------------------------------------------------------------------------- 1 | //go:generate enumer -type=Semver 2 | /* 3 | 4 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 5 | SPDX-License-Identifier: Apache-2.0 6 | Copyright 2017 Capital One Services, LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and limitations under the License. 18 | 19 | */ 20 | package model 21 | 22 | type Semver int 23 | 24 | const ( 25 | Patch Semver = iota 26 | Major 27 | Minor 28 | None 29 | ) 30 | -------------------------------------------------------------------------------- /model/semver_enumer.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=Semver"; DO NOT EDIT 2 | /* 3 | 4 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 5 | SPDX-License-Identifier: Apache-2.0 6 | Copyright 2017 Capital One Services, LLC 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and limitations under the License. 18 | 19 | */ 20 | package model 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | ) 26 | 27 | // Semver is an enum. 28 | // It is serialized as a string in JSON and as an integer in SQL. 29 | 30 | // Semver enum maps. 31 | var ( 32 | strMapSemver = map[string]Semver{ 33 | "major": Major, 34 | "minor": Minor, 35 | "patch": Patch, 36 | "none": None, 37 | } 38 | 39 | intMapSemver = map[Semver]string{ 40 | Major: "major", 41 | Minor: "minor", 42 | Patch: "patch", 43 | None: "none", 44 | } 45 | ) 46 | 47 | // Known says whether or not this value is a known enum value. 48 | func (s Semver) Known() bool { 49 | _, ok := intMapSemver[s] 50 | return ok 51 | } 52 | 53 | // String is for the standard stringer interface. 54 | func (s Semver) String() string { 55 | return intMapSemver[s] 56 | } 57 | 58 | // UnmarshalJSON satisfies the json.Unmarshaler 59 | func (s *Semver) UnmarshalJSON(data []byte) error { 60 | str := "" 61 | err := json.Unmarshal(data, &str) 62 | if err != nil { 63 | return err 64 | } 65 | var ok bool 66 | *s, ok = strMapSemver[str] 67 | if !ok { 68 | return fmt.Errorf("Unknown Semver enum value: %s", str) 69 | } 70 | return nil 71 | } 72 | 73 | // MarshalJSON satisfies the json.Marshaler 74 | func (s Semver) MarshalJSON() ([]byte, error) { 75 | if !s.Known() { 76 | return nil, fmt.Errorf("Unknown Semver enum value: %d", int(s)) 77 | } 78 | name := intMapSemver[s] 79 | return json.Marshal(name) 80 | } 81 | -------------------------------------------------------------------------------- /model/snapshot.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import "github.com/capitalone/checks-out/set" 22 | 23 | type MaintainerSnapshot struct { 24 | People map[string]*Person 25 | Org map[string]Org 26 | } 27 | 28 | func (m *MaintainerSnapshot) PersonToOrg() (map[string]set.Set, error) { 29 | mapping := make(map[string]set.Set) 30 | for k, v := range m.Org { 31 | //value is name of person in the org 32 | people, err := v.GetPeople() 33 | if err != nil { 34 | return nil, err 35 | } 36 | for name := range people { 37 | if _, ok := m.People[name]; !ok { 38 | continue 39 | } 40 | orgs, ok := mapping[name] 41 | if !ok { 42 | orgs = set.Empty() 43 | mapping[name] = orgs 44 | } 45 | orgs.Add(k) 46 | } 47 | } 48 | return mapping, nil 49 | } 50 | -------------------------------------------------------------------------------- /model/tag_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | import ( 22 | "testing" 23 | ) 24 | 25 | func TestCheckRefFormat(t *testing.T) { 26 | cfg := TagConfig{TemplateRaw: "hello world"} 27 | if cfg.Compile() == nil { 28 | t.Error("space character should not pass validation") 29 | } 30 | cfg = TagConfig{TemplateRaw: "{{ .Version }}"} 31 | if cfg.Compile() != nil { 32 | t.Error("space character in template should pass validation") 33 | } 34 | } 35 | 36 | func TestTagValidateDocker(t *testing.T) { 37 | cfg := TagConfig{TemplateRaw: "hello+world", Docker: true} 38 | if cfg.Compile() == nil { 39 | t.Error("+ character should not pass validation") 40 | } 41 | cfg = TagConfig{TemplateRaw: `{{ .Version }}`, Docker: true} 42 | if cfg.Compile() != nil { 43 | t.Error("This should work") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /model/user.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package model 20 | 21 | type User struct { 22 | ID int64 `json:"id" meddler:"user_id,pk"` 23 | Login string `json:"login" meddler:"user_login"` 24 | Token string `json:"-" meddler:"user_token"` 25 | Avatar string `json:"avatar" meddler:"user_avatar"` 26 | Secret string `json:"-" meddler:"user_secret"` 27 | Scopes string `json:"-" meddler:"user_scopes"` 28 | } 29 | -------------------------------------------------------------------------------- /model/util.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | ) 7 | 8 | // standard characters allowed in token string. 9 | var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") 10 | 11 | // default token length 12 | var length = 32 13 | 14 | // Rand generates a 32-bit random string. 15 | func Rand() string { 16 | b := make([]byte, length) 17 | r := make([]byte, length+(length/4)) // storage for random bytes. 18 | clen := byte(len(chars)) 19 | maxrb := byte(256 - (256 % len(chars))) 20 | i := 0 21 | for { 22 | io.ReadFull(rand.Reader, r) 23 | for _, c := range r { 24 | if c >= maxrb { 25 | // Skip this number to avoid modulo bias. 26 | continue 27 | } 28 | b[i] = chars[c%clen] 29 | i++ 30 | if i == length { 31 | return string(b) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /notifier/context.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package notifier 20 | 21 | import "context" 22 | 23 | const key = "sender" 24 | 25 | // Setter defines a context that enables setting values. 26 | type Setter interface { 27 | Set(string, interface{}) 28 | } 29 | 30 | // FromContext returns the Sender associated with this context. 31 | func FromContext(c context.Context) Sender { 32 | return c.Value(key).(Sender) 33 | } 34 | 35 | // ToContext adds the Sender to this context if it supports 36 | // the Setter interface. 37 | func ToContext(c Setter, s Sender) { 38 | c.Set(key, s) 39 | } 40 | -------------------------------------------------------------------------------- /notifier/github/github.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package github 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | 25 | log "github.com/Sirupsen/logrus" 26 | "github.com/capitalone/checks-out/model" 27 | "github.com/capitalone/checks-out/notifier" 28 | "github.com/capitalone/checks-out/remote" 29 | "github.com/capitalone/checks-out/web" 30 | ) 31 | 32 | type MySender struct{} 33 | 34 | func (ms *MySender) Prefix(mw notifier.MessageWrapper) string { 35 | return fmt.Sprintf("Pull Request %s in repo %s: ", mw.MessageHeader.PrName, mw.MessageHeader.Slug) 36 | } 37 | 38 | func (ms *MySender) Send(c context.Context, header notifier.MessageHeader, message string, names []string, url string) { 39 | if header.PrNumber <= 0 { 40 | return 41 | } 42 | repo, user, caps, err := web.GetRepoAndUser(c, header.Slug) 43 | if err != nil { 44 | log.Warnf("Error retrieving GitHub information: %v", err) 45 | return 46 | } 47 | if !caps.Repo.PRWriteComment { 48 | return 49 | } 50 | 51 | //ignore names 52 | err = remote.WriteComment(c, user, repo, header.PrNumber, message) 53 | if err != nil { 54 | log.Warnf("Error sending GitHub notification: %v", err) 55 | } 56 | } 57 | 58 | func init() { 59 | notifier.Register(model.Github, &MySender{}) 60 | } 61 | -------------------------------------------------------------------------------- /notifier/slack/slack.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package slack 20 | 21 | import ( 22 | "bytes" 23 | "context" 24 | "encoding/json" 25 | "fmt" 26 | "net/http" 27 | "strings" 28 | 29 | "github.com/capitalone/checks-out/envvars" 30 | "github.com/capitalone/checks-out/model" 31 | "github.com/capitalone/checks-out/notifier" 32 | 33 | log "github.com/Sirupsen/logrus" 34 | ) 35 | 36 | var ( 37 | githubUrl = envvars.Env.Github.Url 38 | httpClient = &http.Client{} 39 | ) 40 | 41 | func init() { 42 | notifier.Register(model.Slack, &MySender{}) 43 | } 44 | 45 | type MySender struct{} 46 | 47 | //https://github.com/capitalone/checks-out/pull/205 48 | func (ms *MySender) Prefix(mw notifier.MessageWrapper) string { 49 | // 50 | return fmt.Sprintf("<%s/%s/pull/%d|Pull Request %s> in <%s/%s|repo %s>: ", 51 | githubUrl, 52 | mw.MessageHeader.Slug, 53 | mw.MessageHeader.PrNumber, 54 | mw.MessageHeader.PrName, 55 | githubUrl, 56 | mw.MessageHeader.Slug, 57 | mw.MessageHeader.Slug) 58 | } 59 | 60 | func (ms *MySender) Send(c context.Context, header notifier.MessageHeader, message string, names []string, url string) { 61 | if url == "" { 62 | log.Warn("Error sending to Slack: SLACK_TARGET_URL is not configured") 63 | return 64 | } 65 | 66 | baseURL := c.Value("BASE_URL") 67 | iconURL := "" 68 | if baseURL != nil { 69 | iconURL = baseURL.(string) + "/static/images/meowser.png" 70 | } 71 | 72 | d := struct { 73 | Channel string `json:"channel"` 74 | Text string `json:"text"` 75 | Username string `json:"username"` 76 | IconURL string `json:"icon_url"` 77 | }{ 78 | Text: message, 79 | Username: "Meowser", 80 | IconURL: iconURL, 81 | } 82 | for _, name := range names { 83 | d.Channel = name 84 | output, err := toJson(d) 85 | if err != nil { 86 | log.Warnf("Unable to convert notification to JSON: %v", err) 87 | continue 88 | } 89 | go func(msg string) { 90 | //write to the slack channel 91 | _, err := httpClient.Post(url, "application/json", strings.NewReader(msg)) 92 | if err != nil { 93 | log.Warnf("Error while writing %s to %s: %v", msg, url, err) 94 | } 95 | }(output) 96 | } 97 | } 98 | 99 | func toJson(s interface{}) (string, error) { 100 | var b bytes.Buffer 101 | e := json.NewEncoder(&b) 102 | e.SetEscapeHTML(false) 103 | err := e.Encode(s) 104 | if err != nil { 105 | return "", err 106 | } 107 | return b.String(), nil 108 | } 109 | -------------------------------------------------------------------------------- /notifier/types.go: -------------------------------------------------------------------------------- 1 | package notifier 2 | 3 | // Notification represents a notification that we are sending to a list of 4 | // maintainers indicating a commit is ready for their review and, hopefully, 5 | // approval. 6 | type Notification struct { 7 | Reviewers []*Reviewer 8 | Commit *Commit 9 | } 10 | 11 | // Reviewer represents a repository maintainer or contributor that is being 12 | // notified of a commit to review. 13 | type Reviewer struct { 14 | Login string 15 | Email string 16 | } 17 | 18 | // Commit represents the commit for which we are notifiying the maintainers. 19 | type Commit struct { 20 | Repo string 21 | Message string 22 | Author string 23 | Link string 24 | } 25 | -------------------------------------------------------------------------------- /remote/context.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package remote 20 | 21 | import "context" 22 | 23 | const key = "remote" 24 | 25 | // Setter defines a context that enables setting values. 26 | type Setter interface { 27 | Set(string, interface{}) 28 | } 29 | 30 | // FromContext returns the Remote client associated with this context. 31 | func FromContext(c context.Context) Remote { 32 | return c.Value(key).(Remote) 33 | } 34 | 35 | // ToContext adds the Remote client to this context if it supports 36 | // the Setter interface. 37 | func ToContext(c Setter, client Remote) { 38 | c.Set(key, client) 39 | } 40 | -------------------------------------------------------------------------------- /remote/github/paging.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package github 20 | 21 | import ( 22 | "github.com/google/go-github/github" 23 | ) 24 | 25 | func buildCompleteList(process func(opts *github.ListOptions) (*github.Response, error)) (*github.Response, error) { 26 | var response *github.Response 27 | var err error 28 | opts := &github.ListOptions{} 29 | for { 30 | response, err = process(opts) 31 | if err != nil || response.NextPage == 0 { 32 | break 33 | } 34 | opts.Page = response.NextPage 35 | } 36 | return response, err 37 | } 38 | -------------------------------------------------------------------------------- /remote/github/types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package github 20 | 21 | type Branch struct { 22 | Protection struct { 23 | Enabled bool `json:"enabled"` 24 | Checks struct { 25 | Enforcement string `json:"enforcement_level"` 26 | Contexts []string `json:"contexts"` 27 | } `json:"required_status_checks"` 28 | } `json:"protection"` 29 | } 30 | -------------------------------------------------------------------------------- /remote/types.go: -------------------------------------------------------------------------------- 1 | package remote 2 | 3 | // Account represents a user or team account. 4 | type Account struct { 5 | Login string `json:"login"` 6 | Avatar string `json:"avatar"` 7 | Kind string `json:"type"` 8 | } 9 | 10 | // Issue represents an issue or pull request. 11 | type Issue struct { 12 | Number int `json:"issue"` 13 | Title string `json:"title"` 14 | Author string `json:"author"` 15 | } 16 | 17 | // Comment represents a user comment on an issue 18 | // or pull request. 19 | type Comment struct { 20 | Author string `json:"author"` 21 | Body string `json:"body"` 22 | } 23 | -------------------------------------------------------------------------------- /router/middleware/access/access.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package access 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/remote" 23 | "github.com/capitalone/checks-out/router/middleware/session" 24 | 25 | log "github.com/Sirupsen/logrus" 26 | "github.com/gin-gonic/gin" 27 | ) 28 | 29 | func OwnerAdmin(c *gin.Context) { 30 | var ( 31 | owner = c.Param("owner") 32 | user = session.User(c) 33 | ) 34 | 35 | perm, err := remote.GetOrgPerm(c, user, owner) 36 | if err != nil { 37 | log.Warnf("Cannot find org %s. %s", owner, err) 38 | c.String(404, "Not Found") 39 | c.Abort() 40 | return 41 | } 42 | if !perm.Admin { 43 | log.Warnf("User %s does not have Admin access to org %s", user.Login, owner) 44 | c.String(403, "Insufficient privileges") 45 | c.Abort() 46 | return 47 | } 48 | log.Debugf("User %s granted Admin access to org %s", user.Login, owner) 49 | c.Next() 50 | } 51 | 52 | func RepoAdmin(c *gin.Context) { 53 | var ( 54 | owner = c.Param("owner") 55 | name = c.Param("repo") 56 | user = session.User(c) 57 | ) 58 | 59 | perm, err := remote.GetPerm(c, user, owner, name) 60 | if err != nil { 61 | log.Warnf("Cannot find repository %s/%s. %s", owner, name, err) 62 | c.String(404, "Not Found") 63 | c.Abort() 64 | return 65 | } 66 | if !perm.Admin { 67 | log.Warnf("User %s does not have Admin access to repository %s/%s", user.Login, owner, name) 68 | c.String(403, "Insufficient privileges") 69 | c.Abort() 70 | return 71 | } 72 | log.Debugf("User %s granted Admin access to %s/%s", user.Login, owner, name) 73 | c.Next() 74 | } 75 | 76 | func RepoPull(c *gin.Context) { 77 | var ( 78 | owner = c.Param("owner") 79 | name = c.Param("repo") 80 | user = session.User(c) 81 | ) 82 | 83 | perm, err := remote.GetPerm(c, user, owner, name) 84 | if err != nil { 85 | log.Warnf("Cannot find repository %s/%s. %s", owner, name, err) 86 | c.String(404, "Not Found") 87 | c.Abort() 88 | return 89 | } 90 | if !perm.Pull { 91 | log.Warnf("User %s does not have Pull access to repository %s/%s", user.Login, owner, name) 92 | c.String(404, "Not Found") 93 | c.Abort() 94 | return 95 | } 96 | log.Debugf("User %s granted Pull access to %s/%s", user.Login, owner, name) 97 | c.Next() 98 | } 99 | -------------------------------------------------------------------------------- /router/middleware/cache.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package middleware 20 | 21 | import ( 22 | "time" 23 | 24 | "github.com/gin-gonic/gin" 25 | "github.com/capitalone/checks-out/cache" 26 | "github.com/capitalone/checks-out/envvars" 27 | ) 28 | 29 | func Cache() gin.HandlerFunc { 30 | cache_ := cache.NewTTL(time.Duration(envvars.Env.Cache.CacheTTL)) 31 | return func(c *gin.Context) { 32 | cache.ToContext(c, cache_) 33 | c.Next() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /router/middleware/exterror.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package middleware 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/capitalone/checks-out/exterror" 25 | 26 | log "github.com/Sirupsen/logrus" 27 | "github.com/gin-gonic/gin" 28 | ) 29 | 30 | func ExtError() gin.HandlerFunc { 31 | return func(c *gin.Context) { 32 | c.Next() 33 | errs := c.Errors 34 | if len(errs) == 1 { 35 | logAndRespond(c, errs[0].Err) 36 | } else if len(errs) > 1 { 37 | err := fmt.Errorf("Multiple errors: %s", errs.String()) 38 | err = exterror.ExtError{Status: 500, Err: err} 39 | logAndRespond(c, err) 40 | } 41 | } 42 | } 43 | 44 | func emitLog(c *gin.Context, e exterror.ExtError) { 45 | msg := e.Err.Error() 46 | if e.Status < 500 { 47 | log.Warn(msg) 48 | } else { 49 | log.Error(msg) 50 | for k, v := range c.Request.Header { 51 | log.Errorf("%s: %v", k, v) 52 | } 53 | } 54 | } 55 | 56 | func logAndRespond(c *gin.Context, e error) { 57 | err := exterror.Convert(e) 58 | emitLog(c, err) 59 | c.String(err.Status, err.Error()) 60 | } 61 | -------------------------------------------------------------------------------- /router/middleware/header/header.go: -------------------------------------------------------------------------------- 1 | package header 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | // NoCache is a middleware function that appends headers 11 | // to prevent the client from caching the HTTP response. 12 | func NoCache(c *gin.Context) { 13 | c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") 14 | c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") 15 | c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) 16 | c.Next() 17 | } 18 | 19 | // Options is a middleware function that appends headers 20 | // for options requests and aborts then exits the middleware 21 | // chain and ends the request. 22 | func Options(c *gin.Context) { 23 | if c.Request.Method != "OPTIONS" { 24 | c.Next() 25 | } else { 26 | c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") 27 | c.Header("Access-Control-Allow-Headers", "Authorization") 28 | c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") 29 | c.Header("Content-Type", "application/json") 30 | c.AbortWithStatus(200) 31 | } 32 | } 33 | 34 | // Secure is a middleware function that appends security 35 | // and resource access headers. 36 | func Secure(c *gin.Context) { 37 | c.Header("Access-Control-Allow-Origin", "*") 38 | c.Header("X-Frame-Options", "DENY") 39 | c.Header("X-Content-Type-Options", "nosniff") 40 | c.Header("X-XSS-Protection", "1; mode=block") 41 | if c.Request.TLS != nil { 42 | c.Header("Strict-Transport-Security", "max-age=31536000") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /router/middleware/logrequests.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package middleware 20 | 21 | import ( 22 | "strings" 23 | "time" 24 | 25 | "github.com/capitalone/checks-out/envvars" 26 | "github.com/capitalone/checks-out/exterror" 27 | 28 | "github.com/Sirupsen/logrus" 29 | "github.com/gin-gonic/gin" 30 | ) 31 | 32 | func Ginrus(logger *logrus.Logger, timeFormat string, utc bool) gin.HandlerFunc { 33 | uaslice := strings.Split(envvars.Env.Monitor.UaList, ":") 34 | if len(uaslice) == 1 && len(uaslice[0]) == 0 { 35 | uaslice = nil 36 | } 37 | return func(c *gin.Context) { 38 | start := time.Now() 39 | // some evil middlewares modify this values 40 | path := c.Request.URL.Path 41 | c.Next() 42 | 43 | userAgent := c.Request.UserAgent() 44 | 45 | for _, v := range uaslice { 46 | if strings.Contains(userAgent, v) { 47 | return 48 | } 49 | } 50 | end := time.Now() 51 | latency := end.Sub(start) 52 | if utc { 53 | end = end.UTC() 54 | } 55 | 56 | entry := logger.WithFields(logrus.Fields{ 57 | "status": c.Writer.Status(), 58 | "method": c.Request.Method, 59 | "path": path, 60 | "ip": c.ClientIP(), 61 | "latency": latency, 62 | "user-agent": userAgent, 63 | "time": end.Format(timeFormat), 64 | }) 65 | 66 | if len(c.Errors) > 1 { 67 | // Append error field if this is an erroneous request. 68 | entry.Error(c.Errors.String()) 69 | } else if len(c.Errors) == 1 { 70 | err := c.Errors[0].Err 71 | if exterror.Convert(err).Status < 500 { 72 | entry.Warn(err.Error()) 73 | } else { 74 | entry.Error(err.Error()) 75 | } 76 | } else { 77 | entry.Info() 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /router/middleware/remote.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package middleware 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/remote" 23 | 24 | "github.com/gin-gonic/gin" 25 | ) 26 | 27 | func Remote() gin.HandlerFunc { 28 | return func(c *gin.Context) { 29 | remote.ToContext(c, remote.Get()) 30 | c.Next() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /router/middleware/session/capabilities.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package session 20 | 21 | import ( 22 | "github.com/gin-gonic/gin" 23 | "github.com/capitalone/checks-out/exterror" 24 | "github.com/capitalone/checks-out/model" 25 | "github.com/capitalone/checks-out/remote" 26 | ) 27 | 28 | func Capability(c *gin.Context) *model.Capabilities { 29 | v, ok := c.Get("capabilities") 30 | if !ok { 31 | return nil 32 | } 33 | u, ok := v.(*model.Capabilities) 34 | if !ok { 35 | return nil 36 | } 37 | return u 38 | } 39 | 40 | func SetCapability(c *gin.Context) { 41 | user := User(c) 42 | if user != nil { 43 | caps, err := remote.Capabilities(c, user) 44 | if err != nil { 45 | err2 := exterror.Convert(err) 46 | c.String(err2.Status, err2.Err.Error()) 47 | c.Abort() 48 | return 49 | } 50 | c.Set("capabilities", caps) 51 | } 52 | c.Next() 53 | } 54 | -------------------------------------------------------------------------------- /router/middleware/session/user.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package session 20 | 21 | import ( 22 | "net/http" 23 | 24 | "github.com/capitalone/checks-out/model" 25 | "github.com/capitalone/checks-out/shared/token" 26 | "github.com/capitalone/checks-out/store" 27 | 28 | "github.com/gin-gonic/gin" 29 | ) 30 | 31 | func User(c *gin.Context) *model.User { 32 | v, ok := c.Get("user") 33 | if !ok { 34 | return nil 35 | } 36 | u, ok := v.(*model.User) 37 | if !ok { 38 | return nil 39 | } 40 | return u 41 | } 42 | 43 | func UserMust(c *gin.Context) { 44 | user := User(c) 45 | switch { 46 | case user == nil: 47 | c.String(http.StatusUnauthorized, 48 | "You must be logged in and authorized to use this endpoint") 49 | c.Abort() 50 | default: 51 | c.Next() 52 | } 53 | } 54 | 55 | func SetUser(c *gin.Context) { 56 | var user *model.User 57 | 58 | // authenticates the user via an authentication cookie 59 | // or an auth token. 60 | t, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) { 61 | var err error 62 | user, err = store.GetUserLogin(c, t.Text) 63 | return user.Secret, err 64 | }) 65 | 66 | if err == nil { 67 | c.Set("user", user) 68 | 69 | // if this is a session token (ie not the API token) 70 | // this means the user is accessing with a web browser, 71 | // so we should implement CSRF protection measures. 72 | if t.Kind == token.SessToken { 73 | err = token.CheckCsrf(c.Request, func(t *token.Token) (string, error) { 74 | return user.Secret, nil 75 | }) 76 | // if csrf token validation fails, exit immediately 77 | // with a not authorized error. 78 | if err != nil { 79 | c.String(http.StatusUnauthorized, 80 | "You must be logged in and authorized to use this endpoint") 81 | c.Abort() 82 | return 83 | } 84 | } 85 | } 86 | c.Next() 87 | } 88 | -------------------------------------------------------------------------------- /router/middleware/store.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package middleware 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/gin-gonic/gin" 25 | "github.com/capitalone/checks-out/store" 26 | "github.com/capitalone/checks-out/store/datastore" 27 | ) 28 | 29 | func Store() gin.HandlerFunc { 30 | return func(c *gin.Context) { 31 | store.ToContext(c, datastore.Get()) 32 | c.Next() 33 | } 34 | } 35 | 36 | func BaseURL(c *gin.Context) { 37 | url := c.Request.URL 38 | c.Set("BASE_URL", fmt.Sprintf("%s://%s", url.Scheme, url.Host)) 39 | c.Next() 40 | } 41 | -------------------------------------------------------------------------------- /router/middleware/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package middleware 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/version" 23 | 24 | "github.com/gin-gonic/gin" 25 | ) 26 | 27 | // Version is a middleware function that appends version information 28 | // to the HTTP response. This is intended for debugging and troubleshooting. 29 | func Version(c *gin.Context) { 30 | c.Header("X-CHECKS-OUT-VERSION", version.Version) 31 | c.Next() 32 | } 33 | -------------------------------------------------------------------------------- /set/set_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package set 20 | 21 | import ( 22 | "encoding/json" 23 | "reflect" 24 | "testing" 25 | ) 26 | 27 | func TestAddContains(t *testing.T) { 28 | s := Empty() 29 | s.Add("foo") 30 | s.Add("bar") 31 | s.Add("foo") 32 | if !s.Contains("foo") { 33 | t.Error("Set is missing value 'foo'") 34 | } 35 | if !s.Contains("bar") { 36 | t.Error("Set is missing value 'bar'") 37 | } 38 | if s.Contains("baz") { 39 | t.Error("Set is not missing value 'baz'") 40 | } 41 | } 42 | 43 | func TestNew(t *testing.T) { 44 | s := New("foo", "bar", "foo") 45 | if !s.Contains("foo") { 46 | t.Error("Set is missing value 'foo'") 47 | } 48 | if !s.Contains("bar") { 49 | t.Error("Set is missing value 'bar'") 50 | } 51 | if s.Contains("baz") { 52 | t.Error("Set is not missing value 'baz'") 53 | } 54 | } 55 | 56 | func TestKeys(t *testing.T) { 57 | s := New("foo", "bar", "baz") 58 | keys := s.Keys() 59 | if len(keys) != 3 { 60 | t.Error("Set keys list has incorrect size", keys) 61 | } 62 | } 63 | 64 | func TestPrint(t *testing.T) { 65 | x := New() 66 | y := New("a", "b") 67 | res1 := x.Print(",") 68 | res2 := y.Print(",") 69 | if res1 != "" { 70 | t.Error("Set print incorrect", res1) 71 | } 72 | if res2 != "a,b" { 73 | t.Error("Set print incorrect", res2) 74 | } 75 | } 76 | 77 | func TestIntersection(t *testing.T) { 78 | a := New("a", "b", "c") 79 | b := New("b", "c", "y", "z") 80 | obs := a.Intersection(b) 81 | exp := New("b", "c") 82 | if !reflect.DeepEqual(exp, obs) { 83 | t.Error("Set difference incorrect", obs) 84 | } 85 | obs = b.Intersection(a) 86 | if !reflect.DeepEqual(exp, obs) { 87 | t.Error("Set difference incorrect", obs) 88 | } 89 | } 90 | 91 | func TestJSON(t *testing.T) { 92 | var out Set 93 | in := New("foo", "bar") 94 | text, err := json.Marshal(in) 95 | if err != nil { 96 | t.Fatal("Error marshaling set", err) 97 | } 98 | err = json.Unmarshal(text, &out) 99 | if err != nil { 100 | t.Fatal("Error unmarshaling set", err) 101 | } 102 | if !out.Contains("foo") { 103 | t.Error("Set is missing value 'foo'") 104 | } 105 | if !out.Contains("bar") { 106 | t.Error("Set is missing value 'bar'") 107 | } 108 | if out.Contains("baz") { 109 | t.Error("Set is not missing value 'baz'") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /snapshot/errors.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package snapshot 20 | 21 | import ( 22 | "net/http" 23 | 24 | "github.com/capitalone/checks-out/exterror" 25 | ) 26 | 27 | func badRequest(err error) error { 28 | if err == nil { 29 | return nil 30 | } 31 | return exterror.Create(http.StatusBadRequest, err) 32 | } 33 | -------------------------------------------------------------------------------- /snapshot/maintainer.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package snapshot 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | 25 | "github.com/capitalone/checks-out/model" 26 | "github.com/capitalone/checks-out/set" 27 | ) 28 | 29 | const SelfRepo = "repo-self" 30 | const SelfTeam = "repo-self" 31 | 32 | var ReservedOrgs = set.New("all", "us", "them", "universe") 33 | 34 | // ParseMaintainer parses a projects MAINTAINERS file and returns 35 | // the list of maintainers. 36 | func ParseMaintainer(c context.Context, user *model.User, data []byte, r *model.Repo, typ string) (*model.Maintainer, error) { 37 | switch typ { 38 | case "text": 39 | return parseMaintainerText(c, user, data, r) 40 | case "hjson": 41 | return parseMaintainerHJSON(data) 42 | case "toml": 43 | return parseMaintainerToml(data) 44 | case "legacy": 45 | //try to do toml, then do text -- only for .lgtm files 46 | m, err := parseMaintainerToml(data) 47 | if err != nil { 48 | m, err = parseMaintainerText(c, user, data, r) 49 | } 50 | return m, err 51 | 52 | default: 53 | err := fmt.Errorf("%s is not one of the permitted MAINTAINER types %s", 54 | typ, model.MaintTypes.Keys()) 55 | return nil, badRequest(err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /snapshot/maintainer_hjson.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package snapshot 20 | 21 | import ( 22 | "errors" 23 | "fmt" 24 | 25 | "github.com/capitalone/checks-out/hjson" 26 | "github.com/capitalone/checks-out/model" 27 | 28 | "github.com/mspiegel/go-multierror" 29 | ) 30 | 31 | func parseMaintainerHJSON(data []byte) (*model.Maintainer, error) { 32 | m := new(model.Maintainer) 33 | err := hjson.Unmarshal(data, m) 34 | if err != nil { 35 | return nil, badRequest(err) 36 | } 37 | if m.RawPeople == nil { 38 | err = errors.New("Invalid HJSON format. Missing people section.") 39 | return nil, badRequest(err) 40 | } 41 | err = validateMaintainerHJSON(m) 42 | return m, err 43 | } 44 | 45 | func validateMaintainerHJSON(m *model.Maintainer) error { 46 | var errs error 47 | for k, v := range m.RawPeople { 48 | if len(v.Login) == 0 { 49 | // Populate the login field if it is missing. 50 | v.Login = k 51 | } else if v.Login != k { 52 | err := fmt.Errorf("Mismatched key %s and login field %s", k, v.Login) 53 | errs = multierror.Append(errs, badRequest(err)) 54 | } 55 | } 56 | for k := range m.RawOrg { 57 | if ReservedOrgs.Contains(k) { 58 | err := fmt.Errorf("The organization name %s is a reserved name", k) 59 | errs = multierror.Append(errs, badRequest(err)) 60 | } 61 | } 62 | return errs 63 | } 64 | -------------------------------------------------------------------------------- /snapshot/validate.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package snapshot 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/model" 23 | 24 | multierror "github.com/mspiegel/go-multierror" 25 | ) 26 | 27 | func validateSnapshot(config *model.Config, snapshot *model.MaintainerSnapshot) error { 28 | var errs error 29 | for _, approval := range config.Approvals { 30 | err := approval.Match.Validate(snapshot) 31 | errs = multierror.Append(errs, badRequest(err)) 32 | if approval.AntiMatch != nil { 33 | err := approval.AntiMatch.Validate(snapshot) 34 | errs = multierror.Append(errs, badRequest(err)) 35 | } 36 | if approval.AuthorMatch != nil { 37 | err := approval.AuthorMatch.Validate(snapshot) 38 | errs = multierror.Append(errs, badRequest(err)) 39 | } 40 | } 41 | return errs 42 | } 43 | -------------------------------------------------------------------------------- /store/context.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package store 20 | 21 | import "context" 22 | 23 | const key = "store" 24 | 25 | // Setter defines a context that enables setting values. 26 | type Setter interface { 27 | Set(string, interface{}) 28 | } 29 | 30 | // FromContext returns the Store associated with this context. 31 | func FromContext(c context.Context) Store { 32 | return c.Value(key).(Store) 33 | } 34 | 35 | // ToContext adds the Store to this context if it supports the Setter interface. 36 | func ToContext(c Setter, store Store) { 37 | c.Set(key, store) 38 | } 39 | func AddToContext(c context.Context, store Store) context.Context { 40 | return context.WithValue(c, key, store) 41 | } -------------------------------------------------------------------------------- /store/datastore/datastore_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package datastore 20 | 21 | import ( 22 | "database/sql" 23 | "os" 24 | "github.com/capitalone/checks-out/set" 25 | ) 26 | 27 | // OpenTest opens a new database connection for testing purposes. 28 | // The database driver and connection string are provided by 29 | // environment variables, with fallback to in-memory sqlite. 30 | func openTest() (*sql.DB, set.Set, string) { 31 | var ( 32 | driver = "sqlite3" 33 | config = ":memory:" 34 | ) 35 | if os.Getenv("TEST_DB_DRIVER") != "" && os.Getenv("TEST_DB_SOURCE") != "" { 36 | driver = os.Getenv("TEST_DB_DRIVER") 37 | config = os.Getenv("TEST_DB_SOURCE") 38 | } 39 | db, migrations := Open(driver, config) 40 | return db, migrations, driver 41 | } 42 | -------------------------------------------------------------------------------- /store/migration/migration.go: -------------------------------------------------------------------------------- 1 | package migration 2 | 3 | //go:generate go-bindata -pkg migration -o migration_gen.go sqlite3/ mysql/ postgres/ 4 | -------------------------------------------------------------------------------- /store/migration/mysql/001_init.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS users ( 4 | user_id INTEGER PRIMARY KEY AUTO_INCREMENT 5 | ,user_login VARCHAR(255) 6 | ,user_token VARCHAR(255) 7 | ,user_email VARCHAR(255) 8 | ,user_avatar VARCHAR(1024) 9 | ,user_secret VARCHAR(255) 10 | ,UNIQUE(user_login) 11 | ); 12 | 13 | CREATE TABLE IF NOT EXISTS repos ( 14 | repo_id INTEGER PRIMARY KEY AUTO_INCREMENT 15 | ,repo_user_id INTEGER 16 | ,repo_owner VARCHAR(255) 17 | ,repo_name VARCHAR(255) 18 | ,repo_slug VARCHAR(255) 19 | ,repo_link VARCHAR(1024) 20 | ,repo_private BOOLEAN 21 | ,repo_secret VARCHAR(255) 22 | ,UNIQUE(repo_slug) 23 | ); 24 | 25 | ALTER TABLE repos ADD INDEX (repo_owner); 26 | ALTER TABLE repos ADD INDEX (repo_user_id); 27 | 28 | -- +migrate Down 29 | 30 | DROP TABLE repos; 31 | DROP TABLE users; 32 | -------------------------------------------------------------------------------- /store/migration/mysql/002_org.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE repos ADD COLUMN repo_org BOOLEAN DEFAULT FALSE; 4 | 5 | -- +migrate Down 6 | 7 | ALTER TABLE repos DROP COLUMN repo_org; -------------------------------------------------------------------------------- /store/migration/mysql/003_drop_emails.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE users DROP COLUMN user_email; 4 | 5 | -- +migrate Down 6 | 7 | ALTER TABLE users ADD COLUMN user_email VARCHAR(255) DEFAULT ''; -------------------------------------------------------------------------------- /store/migration/mysql/004_limit_users.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS limit_users ( 4 | login VARCHAR(255) NOT NULL 5 | ,UNIQUE(login) 6 | ); 7 | 8 | CREATE TABLE IF NOT EXISTS limit_orgs ( 9 | org VARCHAR(255) NOT NULL 10 | , UNIQUE(org) 11 | ); 12 | 13 | -- +migrate Down 14 | 15 | DROP TABLE limit_users; 16 | 17 | DROP TABLE limit_orgs; 18 | -------------------------------------------------------------------------------- /store/migration/mysql/005_oauth_scope.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE users ADD COLUMN user_scopes VARCHAR(255) DEFAULT ''; 4 | 5 | -- +migrate Down 6 | 7 | ALTER TABLE users DROP COLUMN user_scopes; -------------------------------------------------------------------------------- /store/migration/mysql/006_add_orgs_table.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS orgs ( 4 | org_id INTEGER PRIMARY KEY AUTO_INCREMENT 5 | ,org_user_id INTEGER 6 | ,org_owner VARCHAR(255) 7 | ,org_link VARCHAR(1024) 8 | ,org_private BOOLEAN 9 | ,org_secret VARCHAR(255) 10 | ,UNIQUE(org_owner) 11 | ); 12 | 13 | ALTER TABLE orgs ADD INDEX (org_owner); 14 | ALTER TABLE orgs ADD INDEX (org_user_id); 15 | 16 | -- +migrate Down 17 | 18 | DROP TABLE orgs; 19 | -------------------------------------------------------------------------------- /store/migration/mysql/007_add_slack_urls.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | create table if not exists slack_urls( 4 | id integer primary key AUTO_INCREMENT, 5 | host_name VARCHAR(255) not null, 6 | user VARCHAR(255) not null, 7 | url VARCHAR(1024) not null, 8 | unique(host_name, user) 9 | ); 10 | 11 | -- +migrate Down 12 | 13 | DROP TABLE slack_urls; 14 | -------------------------------------------------------------------------------- /store/migration/postgres/001_init.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS users ( 4 | user_id BIGSERIAL PRIMARY KEY 5 | ,user_login TEXT 6 | ,user_token TEXT 7 | ,user_email TEXT 8 | ,user_avatar TEXT 9 | ,user_secret TEXT 10 | 11 | ,UNIQUE(user_login) 12 | ); 13 | 14 | CREATE TABLE IF NOT EXISTS repos ( 15 | repo_id BIGSERIAL PRIMARY KEY 16 | ,repo_user_id INTEGER 17 | ,repo_owner TEXT 18 | ,repo_name TEXT 19 | ,repo_slug TEXT 20 | ,repo_link TEXT 21 | ,repo_private BOOLEAN 22 | ,repo_secret TEXT 23 | 24 | ,UNIQUE(repo_slug) 25 | ); 26 | 27 | CREATE INDEX IF NOT EXISTS ix_repo_owner ON repos (repo_owner); 28 | CREATE INDEX IF NOT EXISTS ix_repo_user_id ON repos (repo_user_id); 29 | 30 | -- +migrate Down 31 | 32 | DROP TABLE repos; 33 | DROP TABLE users; 34 | -------------------------------------------------------------------------------- /store/migration/postgres/002_org.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE repos ADD COLUMN repo_org BOOLEAN DEFAULT FALSE; 4 | 5 | -- +migrate Down 6 | 7 | ALTER TABLE repos DROP COLUMN repo_org; -------------------------------------------------------------------------------- /store/migration/postgres/003_drop_emails.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE users DROP COLUMN user_email; 4 | 5 | -- +migrate Down 6 | 7 | ALTER TABLE users ADD COLUMN user_email VARCHAR(255) DEFAULT ''; -------------------------------------------------------------------------------- /store/migration/postgres/004_limit_users.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS limit_users ( 4 | login TEXT 5 | ,UNIQUE(login) 6 | ); 7 | 8 | CREATE TABLE IF NOT EXISTS limit_orgs ( 9 | org TEXT 10 | , UNIQUE(org) 11 | ); 12 | 13 | -- +migrate Down 14 | 15 | DROP TABLE limit_users; 16 | 17 | DROP TABLE limit_orgs; 18 | -------------------------------------------------------------------------------- /store/migration/postgres/005_oauth_scope.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE users ADD COLUMN user_scopes VARCHAR(255) DEFAULT ''; 4 | 5 | -- +migrate Down 6 | 7 | ALTER TABLE users DROP COLUMN user_scopes; -------------------------------------------------------------------------------- /store/migration/postgres/006_add_orgs_table.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS orgs ( 4 | org_id BIGSERIAL PRIMARY KEY 5 | ,org_user_id INTEGER 6 | ,org_owner VARCHAR(255) 7 | ,org_link VARCHAR(1024) 8 | ,org_private BOOLEAN 9 | ,org_secret VARCHAR(255) 10 | ,UNIQUE(org_owner) 11 | ); 12 | 13 | CREATE INDEX IF NOT EXISTS ix_org_owner ON orgs (org_owner); 14 | CREATE INDEX IF NOT EXISTS ix_org_user_id ON orgs (org_user_id); 15 | 16 | -- +migrate Down 17 | 18 | DROP TABLE orgs; 19 | -------------------------------------------------------------------------------- /store/migration/postgres/007_add_slack_urls.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | create table if not exists slack_urls( 4 | id BIGSERIAL PRIMARY KEY, 5 | host_name text not null, 6 | user text not null, 7 | url text not null, 8 | unique(host_name, user) 9 | ); 10 | 11 | -- +migrate Down 12 | 13 | DROP TABLE slack_urls; 14 | -------------------------------------------------------------------------------- /store/migration/sqlite3/001_init.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS users ( 4 | user_id INTEGER PRIMARY KEY AUTOINCREMENT 5 | ,user_login TEXT 6 | ,user_token TEXT 7 | ,user_email TEXT 8 | ,user_avatar TEXT 9 | ,user_secret TEXT 10 | 11 | ,UNIQUE(user_login) 12 | ); 13 | 14 | CREATE TABLE IF NOT EXISTS repos ( 15 | repo_id INTEGER PRIMARY KEY AUTOINCREMENT 16 | ,repo_user_id INTEGER 17 | ,repo_owner TEXT 18 | ,repo_name TEXT 19 | ,repo_slug TEXT 20 | ,repo_link TEXT 21 | ,repo_private BOOLEAN 22 | ,repo_secret TEXT 23 | 24 | ,UNIQUE(repo_slug) 25 | ); 26 | 27 | CREATE INDEX IF NOT EXISTS ix_repo_owner ON repos (repo_owner); 28 | CREATE INDEX IF NOT EXISTS ix_repo_user_id ON repos (repo_user_id); 29 | 30 | -- +migrate Down 31 | 32 | DROP TABLE repos; 33 | DROP TABLE users; 34 | -------------------------------------------------------------------------------- /store/migration/sqlite3/002_org.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE repos ADD COLUMN repo_org BOOLEAN DEFAULT FALSE; 4 | -------------------------------------------------------------------------------- /store/migration/sqlite3/003_drop_emails.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE users RENAME TO temp_users; 4 | 5 | CREATE TABLE IF NOT EXISTS users ( 6 | user_id INTEGER PRIMARY KEY AUTOINCREMENT 7 | ,user_login TEXT 8 | ,user_token TEXT 9 | ,user_avatar TEXT 10 | ,user_secret TEXT 11 | 12 | ,UNIQUE(user_login) 13 | ); 14 | 15 | INSERT INTO users 16 | SELECT 17 | user_id, user_login, user_token, user_avatar, user_secret 18 | FROM 19 | temp_users; 20 | 21 | DROP TABLE temp_users; 22 | 23 | -- +migrate Down 24 | 25 | ALTER TABLE users ADD COLUMN user_email TEXT DEFAULT ''; -------------------------------------------------------------------------------- /store/migration/sqlite3/004_limit_users.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS limit_users ( 4 | login TEXT 5 | ,UNIQUE(login) 6 | ); 7 | 8 | CREATE TABLE IF NOT EXISTS limit_orgs ( 9 | org TEXT 10 | , UNIQUE(org) 11 | ); 12 | 13 | -- +migrate Down 14 | 15 | DROP TABLE limit_users; 16 | 17 | DROP TABLE limit_orgs; 18 | -------------------------------------------------------------------------------- /store/migration/sqlite3/005_oauth_scope.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | ALTER TABLE users ADD COLUMN user_scopes TEXT DEFAULT ''; 4 | -------------------------------------------------------------------------------- /store/migration/sqlite3/006_add_orgs_table.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | CREATE TABLE IF NOT EXISTS orgs ( 4 | org_id INTEGER PRIMARY KEY AUTOINCREMENT, 5 | org_user_id INTEGER, 6 | org_owner VARCHAR(255), 7 | org_link VARCHAR(1024), 8 | org_private BOOLEAN, 9 | org_secret VARCHAR(255), 10 | UNIQUE (org_owner) 11 | ); 12 | 13 | CREATE INDEX IF NOT EXISTS ix_org_owner ON orgs (org_owner); 14 | CREATE INDEX IF NOT EXISTS ix_org_user_id ON orgs (org_user_id); 15 | 16 | -- +migrate Down 17 | 18 | DROP TABLE orgs; 19 | -------------------------------------------------------------------------------- /store/migration/sqlite3/007_add_slack_urls.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Up 2 | 3 | create table if not exists slack_urls( 4 | id integer primary key autoincrement, 5 | host_name text not null, 6 | user text not null, 7 | url text not null, 8 | unique(host_name, user) 9 | ); 10 | 11 | -- +migrate Down 12 | 13 | DROP TABLE slack_urls; 14 | -------------------------------------------------------------------------------- /store/store_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package store 20 | 21 | import ( 22 | "context" 23 | "testing" 24 | "time" 25 | 26 | "github.com/capitalone/checks-out/cache" 27 | ) 28 | 29 | type mockStore struct { 30 | Store 31 | callCount int 32 | } 33 | 34 | func (ms *mockStore) GetValidOrgs() ([]string, error) { 35 | ms.callCount++ 36 | return []string{"beatles", "stones", "ledzep"}, nil 37 | } 38 | 39 | func TestGetValidOrgs(t *testing.T) { 40 | s := &mockStore{} 41 | 42 | c := context.Background() 43 | c = context.WithValue(c, "store", s) 44 | c = context.WithValue(c, "cache", cache.NewTTL(40*time.Millisecond)) 45 | 46 | orgs, err := GetValidOrgs(c) 47 | validHelper := func() { 48 | if err != nil { 49 | t.Fatalf("Expected no error, got %v", err) 50 | } 51 | if orgs == nil { 52 | t.Fatal("Expected non-nil Set returned") 53 | } 54 | if !orgs.Contains("beatles") { 55 | t.Error("Should have contained beatles") 56 | } 57 | if !orgs.Contains("stones") { 58 | t.Error("Should have contained stones") 59 | } 60 | if !orgs.Contains("ledzep") { 61 | t.Error("Should have contained ledzep") 62 | } 63 | } 64 | validHelper() 65 | if s.callCount != 1 { 66 | t.Errorf("Expected 1 call to store, got %d", s.callCount) 67 | } 68 | //test caching is doing its job 69 | orgs, err = GetValidOrgs(c) 70 | validHelper() 71 | if s.callCount != 1 { 72 | t.Errorf("Expected 1 call to store, got %d", s.callCount) 73 | } 74 | //sleep to let cache expire 75 | time.Sleep(50 * time.Millisecond) 76 | orgs, err = GetValidOrgs(c) 77 | validHelper() 78 | if s.callCount != 2 { 79 | t.Errorf("Expected 2 calls to store, got %d", s.callCount) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /strings/lowercase/lowercase.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package lowercase 20 | 21 | import ( 22 | "strings" 23 | ) 24 | 25 | type String struct { 26 | val string 27 | } 28 | 29 | func Create(s string) String { 30 | return String{strings.ToLower(s)} 31 | } 32 | 33 | func (l String) String() string { 34 | return l.val 35 | } 36 | 37 | type Slice []String 38 | 39 | func CreateSlice(ss ...string) Slice { 40 | var out Slice 41 | for _, v := range ss { 42 | out = append(out, Create(v)) 43 | } 44 | return out 45 | } 46 | 47 | func (ls Slice) ToStringSlice() []string { 48 | var out []string 49 | for _, v := range ls { 50 | out = append(out, v.String()) 51 | } 52 | return out 53 | } 54 | -------------------------------------------------------------------------------- /strings/lowercase/lowercase_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package lowercase 20 | 21 | import ( 22 | "testing" 23 | ) 24 | 25 | func TestCreate(t *testing.T) { 26 | l := Create("Foobar") 27 | if l.val != "foobar" { 28 | t.Error("Lowercase value generated incorrectly") 29 | } 30 | } 31 | 32 | func TestLowercase(t *testing.T) { 33 | l := Create("Foobar") 34 | if l.String() != "foobar" { 35 | t.Error("Lowercase value retrieved incorrectly") 36 | } 37 | } 38 | 39 | func TestEmpty(t *testing.T) { 40 | var l String 41 | if l.String() != "" { 42 | t.Error("empty lowercase not handled correctly") 43 | } 44 | } -------------------------------------------------------------------------------- /strings/miniglob/miniglob.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package miniglob 20 | 21 | import ( 22 | "bytes" 23 | "encoding/json" 24 | "regexp" 25 | "strings" 26 | ) 27 | 28 | type MiniGlob struct { 29 | Regex *regexp.Regexp 30 | Text string 31 | } 32 | 33 | func Pattern(text string) string { 34 | var buffer bytes.Buffer 35 | buffer.WriteString("^") 36 | text = strings.TrimSpace(text) 37 | text = strings.TrimPrefix(text, "/") 38 | text = strings.TrimSuffix(text, "/") 39 | outer := strings.Split(text, "**") 40 | for i, sec1 := range outer { 41 | inner := strings.Split(sec1, "*") 42 | for j, sec2 := range inner { 43 | buffer.WriteString(regexp.QuoteMeta(sec2)) 44 | if j < len(inner)-1 { 45 | buffer.WriteString("[^/]*") 46 | } 47 | } 48 | if i < len(outer)-1 { 49 | buffer.WriteString(".*") 50 | } 51 | } 52 | buffer.WriteString("$") 53 | return buffer.String() 54 | } 55 | 56 | func Compile(text string) (*regexp.Regexp, error) { 57 | return regexp.Compile(Pattern(text)) 58 | } 59 | 60 | func Create(text string) (MiniGlob, error) { 61 | regex, err := Compile(text) 62 | if err != nil { 63 | return MiniGlob{}, err 64 | } 65 | return MiniGlob{Text: text, Regex: regex}, nil 66 | } 67 | 68 | func MustCreate(text string) MiniGlob { 69 | glob, err := Create(text) 70 | if err != nil { 71 | panic(err) 72 | } 73 | return glob 74 | } 75 | 76 | func (rs MiniGlob) MarshalJSON() ([]byte, error) { 77 | var buf bytes.Buffer 78 | e := json.NewEncoder(&buf) 79 | e.SetEscapeHTML(false) 80 | err := e.Encode(rs.Text) 81 | if err != nil { 82 | return nil, err 83 | } 84 | // golang json encoder adds a newline for each value 85 | return bytes.TrimSpace(buf.Bytes()), nil 86 | } 87 | 88 | func (rs *MiniGlob) UnmarshalJSON(text []byte) error { 89 | var pat string 90 | err := json.Unmarshal(text, &pat) 91 | if err != nil { 92 | return err 93 | } 94 | r, err := Create(pat) 95 | if err != nil { 96 | return err 97 | } 98 | *rs = r 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /strings/miniglob/miniglob_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package miniglob 20 | 21 | import ( 22 | "encoding/json" 23 | "testing" 24 | ) 25 | 26 | func testPattern(t *testing.T, in string, out string) { 27 | obs := Pattern(in) 28 | if obs != out { 29 | t.Errorf("Pattern compilation of %s expected %s and observed %s", 30 | in, out, obs) 31 | } 32 | } 33 | 34 | func TestPattern(t *testing.T) { 35 | testPattern(t, "", "^$") 36 | testPattern(t, "foo", "^foo$") 37 | testPattern(t, "/foo/bar/", "^foo/bar$") 38 | testPattern(t, "*.java", "^[^/]*\\.java$") 39 | testPattern(t, "**.java", "^.*\\.java$") 40 | testPattern(t, "**/*.java", "^.*/[^/]*\\.java$") 41 | } 42 | 43 | func TestSerde(t *testing.T) { 44 | var body MiniGlob 45 | err := json.Unmarshal([]byte("\" /hello*world/ \""), &body) 46 | if err != nil { 47 | t.Fatal("Unmarshal failure", err) 48 | } 49 | if body.Text != " /hello*world/ " { 50 | t.Errorf("Unmarshal text context not successful: %s", body.Text) 51 | } 52 | if body.Regex.String() != "^hello[^/]*world$" { 53 | t.Errorf("Unmarshal regex context not successful: %s", body.Regex.String()) 54 | } 55 | out, err := json.Marshal(body) 56 | if err != nil { 57 | t.Fatal("Marshal failure", err) 58 | } 59 | if string(out) != "\" /hello*world/ \"" { 60 | t.Errorf("Marshal context not successful: %s", string(out)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /strings/rxserde/regexp.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package rxserde 20 | 21 | import ( 22 | "bytes" 23 | "encoding/json" 24 | "regexp" 25 | ) 26 | 27 | type RegexSerde struct { 28 | Regex *regexp.Regexp 29 | } 30 | 31 | func (rs RegexSerde) MarshalJSON() ([]byte, error) { 32 | var buf bytes.Buffer 33 | e := json.NewEncoder(&buf) 34 | e.SetEscapeHTML(false) 35 | err := e.Encode(rs.Regex.String()) 36 | if err != nil { 37 | return nil, err 38 | } 39 | // golang json encoder adds a newline for each value 40 | return bytes.TrimSpace(buf.Bytes()), nil 41 | } 42 | 43 | func (rs *RegexSerde) UnmarshalJSON(text []byte) error { 44 | var pat string 45 | err := json.Unmarshal(text, &pat) 46 | if err != nil { 47 | return err 48 | } 49 | r, err := regexp.Compile(pat) 50 | if err != nil { 51 | return err 52 | } 53 | rs.Regex = r 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /strings/rxserde/regexp_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package rxserde 20 | 21 | import ( 22 | "bytes" 23 | "encoding/json" 24 | "regexp" 25 | "testing" 26 | ) 27 | 28 | type Testinner struct { 29 | Pattern RegexSerde 30 | } 31 | 32 | const text = `{ 33 | "pattern": "abcdef" 34 | }` 35 | 36 | const expected = `"(?i)^I approve\\s*(?P\\S*)"` 37 | 38 | func TestMarshalDirect(t *testing.T) { 39 | r := RegexSerde{regexp.MustCompile(`(?i)^I approve\s*(?P\S*)`)} 40 | b, err := r.MarshalJSON() 41 | if err != nil { 42 | t.Fatal("Unable to marshal regex serde", err) 43 | } 44 | s := string(b) 45 | if s != `"(?i)^I approve\\s*(?P\\S*)"` { 46 | t.Error("marshal regex serde did not yield expected result", s) 47 | } 48 | } 49 | 50 | func TestMarshalRegexSerde(t *testing.T) { 51 | r := RegexSerde{regexp.MustCompile(`(?i)^I approve\s*(?P\S*)`)} 52 | var buf bytes.Buffer 53 | e := json.NewEncoder(&buf) 54 | e.SetEscapeHTML(false) 55 | err := e.Encode(r) 56 | if err != nil { 57 | t.Fatal("Unable to marshal regex serde", err) 58 | } 59 | s := string(bytes.TrimSpace(buf.Bytes())) 60 | if s != `"(?i)^I approve\\s*(?P\\S*)"` { 61 | t.Error("marshal regex serde did not yield expected result", s) 62 | } 63 | } 64 | 65 | func TestInnerRegexPattern(t *testing.T) { 66 | var result Testinner 67 | err := json.Unmarshal([]byte(text), &result) 68 | if err != nil { 69 | t.Fatal("Unable to unmarshal test struct", err) 70 | } 71 | if result.Pattern.Regex.String() != "abcdef" { 72 | t.Error("Unable to read inner pattern", result) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /usage/usage_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package usage 20 | 21 | import ( 22 | "testing" 23 | "context" 24 | ) 25 | 26 | func TestContextUnique(t *testing.T) { 27 | c := context.Background() 28 | c2 := AddEventToContext(c, "Hello") 29 | i := 0 30 | v := c2.Value(i) 31 | if v != nil { 32 | t.Error("Should not be able to get value without opaque type") 33 | } 34 | v2 := GetEventFromContext(c2) 35 | if v2 != "Hello" { 36 | t.Errorf("Expected Hello, got %v",v2) 37 | } 38 | } -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var Version = "dev" 4 | -------------------------------------------------------------------------------- /web/approval_hook.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "context" 23 | 24 | "github.com/capitalone/checks-out/notifier" 25 | ) 26 | 27 | func doApprovalHook(c context.Context, hook *ApprovalHook, isApp IsApprover) (*ApprovalOutput, error) { 28 | params, err := GetHookParameters(c, hook.HookCommon, hook.Repo.Slug) 29 | if err != nil { 30 | return nil, err 31 | } 32 | params.Approval = isApp 33 | approvalInfo, err := approve(c, params, hook.Issue.Number, true) 34 | 35 | if err != nil { 36 | notifier.SendErrorMessage(c, params.Config, hook.Issue.Title, 37 | hook.Issue.Number, hook.Repo.Slug, err.Error()) 38 | return nil, err 39 | } 40 | mw := handleApprovalNotification(hook, &approvalInfo.CurCommentInfo) 41 | notifier.SendMessage(c, params.Config, *mw) 42 | approvalOutput := ApprovalOutput{ 43 | Policy: approvalInfo.Policy, 44 | Settings: params.Config, 45 | Approved: approvalInfo.Approved, 46 | Approvers: approvalInfo.Approvers, 47 | Disapprovers: approvalInfo.Disapprovers, 48 | } 49 | return &approvalOutput, nil 50 | } 51 | -------------------------------------------------------------------------------- /web/comment_hook.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "context" 23 | "strings" 24 | 25 | "github.com/capitalone/checks-out/model" 26 | 27 | multierror "github.com/mspiegel/go-multierror" 28 | ) 29 | 30 | func (hook *CommentHook) Process(c context.Context) (interface{}, error) { 31 | approvalOutput, e1 := doCommentHook(c, hook) 32 | if e1 != nil { 33 | e2 := sendErrorStatus(c, &hook.ApprovalHook, e1) 34 | e1 = multierror.Append(e1, e2) 35 | } 36 | return approvalOutput, e1 37 | } 38 | 39 | func doCommentHook(c context.Context, hook *CommentHook) (*ApprovalOutput, error) { 40 | if strings.HasPrefix(hook.Comment, model.CommentPrefix) { 41 | return nil, nil 42 | } 43 | return doApprovalHook(c, &hook.ApprovalHook, hook) 44 | } 45 | -------------------------------------------------------------------------------- /web/endpoints.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "path" 23 | "strconv" 24 | 25 | "github.com/gin-gonic/gin" 26 | ) 27 | 28 | // ApprovalStatus generates a response with the current status of a pull request 29 | func ApprovalStatus(c *gin.Context) { 30 | var ( 31 | owner = c.Param("owner") 32 | name = c.Param("repo") 33 | id = c.Param("id") 34 | ) 35 | 36 | pr, err := strconv.Atoi(id) 37 | if err != nil { 38 | c.String(400, "Unable to convert pull request id %s to number", id) 39 | return 40 | } 41 | 42 | params, err := GetHookParametersBasic(c, path.Join(owner, name)) 43 | if err != nil { 44 | c.Error(err) 45 | return 46 | } 47 | approvalInfo, err := approve(c, params, pr, false) 48 | if err != nil { 49 | c.Error(err) 50 | } else { 51 | c.IndentedJSON(200, gin.H{ 52 | "policy": approvalInfo.Policy, 53 | "settings": params.Config, 54 | "approved": approvalInfo.Approved, 55 | "approvers": approvalInfo.Approvers, 56 | "disapprovers": approvalInfo.Disapprovers, 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /web/hook.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "context" 23 | 24 | "github.com/capitalone/checks-out/model" 25 | "github.com/capitalone/checks-out/strings/lowercase" 26 | "github.com/gin-gonic/gin" 27 | ) 28 | 29 | type Hook interface { 30 | Process(c context.Context) (interface{}, error) 31 | SetEvent(event string) 32 | } 33 | 34 | type IsApprover interface { 35 | IsApproval(req *model.ApprovalRequest) bool 36 | } 37 | 38 | type HookCommon struct { 39 | Event string 40 | Action string 41 | } 42 | 43 | type ApprovalHook struct { 44 | HookCommon 45 | Repo *model.Repo 46 | Issue *model.Issue 47 | } 48 | 49 | type CommentHook struct { 50 | ApprovalHook 51 | Comment string 52 | } 53 | 54 | type ReviewHook struct { 55 | ApprovalHook 56 | State lowercase.String 57 | } 58 | 59 | type PRHook struct { 60 | ApprovalHook 61 | PullRequest *model.PullRequest 62 | } 63 | 64 | type RepoHook struct { 65 | HookCommon 66 | Name string 67 | Owner string 68 | BaseURL string 69 | } 70 | 71 | type StatusHook struct { 72 | HookCommon 73 | SHA string 74 | Status *model.CommitStatus 75 | Repo *model.Repo 76 | } 77 | 78 | type HookParams struct { 79 | Repo *model.Repo 80 | User *model.User 81 | Cap *model.Capabilities 82 | Config *model.Config 83 | Snapshot *model.MaintainerSnapshot 84 | Event string 85 | Action string 86 | Approval IsApprover 87 | } 88 | 89 | func ProcessHook(c *gin.Context) { 90 | hook, c2, err := createHook(c, c.Request) 91 | if err != nil { 92 | c.Error(err) 93 | } else if hook == nil { 94 | c.String(200, "pong") 95 | } else { 96 | output, err := hook.Process(c2) 97 | if err != nil { 98 | c.Error(err) 99 | } else { 100 | if output == nil { 101 | c.String(200, "pong") 102 | } else { 103 | c.IndentedJSON(200, output) 104 | } 105 | 106 | } 107 | } 108 | } 109 | 110 | func (h *CommentHook) IsApproval(req *model.ApprovalRequest) bool { 111 | c := model.Comment{ 112 | Body: h.Comment, 113 | } 114 | return c.IsApproval(req) 115 | } 116 | 117 | func (h *ReviewHook) IsApproval(req *model.ApprovalRequest) bool { 118 | r := model.Review{ 119 | State: h.State, 120 | } 121 | return r.IsApproval(req) 122 | } 123 | 124 | func (h *HookCommon) SetEvent(event string) { 125 | h.Event = event 126 | } 127 | -------------------------------------------------------------------------------- /web/index.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Brad Rydzewski, project contributors, Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Brad Rydzewski, project contributors, Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/remote" 23 | "github.com/capitalone/checks-out/router/middleware/session" 24 | "github.com/capitalone/checks-out/shared/token" 25 | 26 | "github.com/gin-gonic/gin" 27 | "github.com/capitalone/checks-out/envvars" 28 | ) 29 | 30 | func Index(c *gin.Context) { 31 | user := session.User(c) 32 | 33 | docsUrl := envvars.Env.Monitor.DocsUrl 34 | 35 | switch { 36 | case user == nil: 37 | c.HTML(200, "brand.html", gin.H{"DocsUrl": docsUrl}) 38 | default: 39 | teams, _ := remote.GetOrgs(c, user) 40 | csrf, _ := token.New(token.CsrfToken, user.Login).Sign(user.Secret) 41 | c.HTML(200, "index.html", gin.H{"user": user, "csrf": csrf, "teams": teams, "docsUrl": docsUrl}) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /web/merge.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "context" 23 | 24 | log "github.com/Sirupsen/logrus" 25 | "github.com/capitalone/checks-out/model" 26 | "github.com/capitalone/checks-out/remote" 27 | ) 28 | 29 | func isBehind(c context.Context, user *model.User, repo *model.Repo, branch model.Branch) (bool, error) { 30 | resp, err := remote.CompareBranches(c, user, repo, branch.BaseName, branch.CompareName, branch.CompareOwner) 31 | if err != nil { 32 | return false, err 33 | } 34 | return resp.BehindBy > 0, nil 35 | } 36 | 37 | func doMerge(c context.Context, user *model.User, 38 | hook *StatusHook, req *model.ApprovalRequest, policy *model.ApprovalPolicy, mergeMethod string) (string, error) { 39 | approvals, err := buildApprovers(c, user, req) 40 | if err != nil { 41 | return "", err 42 | } 43 | var people []*model.Person 44 | for id := range approvals.Approvers { 45 | if p, ok := req.Maintainer.People[id]; ok { 46 | people = append(people, p) 47 | } else { 48 | people = append(people, &model.Person{Login: id}) 49 | } 50 | } 51 | message := getCommitComment(req, policy) 52 | log.Debugf("parsed out commit comment message, got: %v", message) 53 | 54 | SHA, err := remote.MergePR(c, user, hook.Repo, *req.PullRequest, people, message, mergeMethod) 55 | if err != nil { 56 | return "", err 57 | } 58 | return SHA, nil 59 | } 60 | 61 | func doMergeDelete(c context.Context, user *model.User, 62 | hook *StatusHook, req *model.ApprovalRequest) error { 63 | // Head branch contains what changes you like to be applied. 64 | // Do not delete the base branch. 65 | ref := req.PullRequest.Branch.CompareName 66 | return remote.DeleteBranch(c, user, hook.Repo, ref) 67 | } 68 | -------------------------------------------------------------------------------- /web/notification.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "fmt" 23 | 24 | "github.com/capitalone/checks-out/model" 25 | "github.com/capitalone/checks-out/notifier" 26 | 27 | log "github.com/Sirupsen/logrus" 28 | ) 29 | 30 | func handleApprovalNotification(hook *ApprovalHook, curCommentInfo *CurCommentInfo) *notifier.MessageWrapper { 31 | mw := ¬ifier.MessageWrapper{ 32 | MessageHeader: notifier.MessageHeader{ 33 | PrName: hook.Issue.Title, 34 | PrNumber: hook.Issue.Number, 35 | Slug: hook.Repo.Slug, 36 | }, 37 | } 38 | if curCommentInfo != nil { 39 | var mi notifier.MessageInfo 40 | switch curCommentInfo.Status { 41 | case CurCommentNoChange: 42 | // do nothing 43 | case CurCommentApproval: 44 | mi.Message = fmt.Sprintf("approval added by %s.", curCommentInfo.Author) 45 | mi.Type = model.CommentApprove 46 | case CurCommentDisapproval: 47 | mi.Message = fmt.Sprintf("blocked by %s.", curCommentInfo.Author) 48 | mi.Type = model.CommentBlock 49 | case CurCommentPRAuthor: 50 | mi.Message = fmt.Sprintf("blocked because it was created by unapproved author %s.", hook.Issue.Author) 51 | mi.Type = model.CommentBlock 52 | case CurCommentPRTitle: 53 | mi.Message = "blocked because its title indicates that it should not be merged" 54 | mi.Type = model.CommentBlock 55 | case CurCommentPRAudit: 56 | mi.Message = "blocked by gap in audit chain" 57 | mi.Type = model.CommentBlock 58 | default: 59 | log.Warnf("Invalid curCommentInfo.Status found, skipping: %v", curCommentInfo.Status) 60 | } 61 | 62 | if mi.Message != "" { 63 | mw.Messages = append(mw.Messages, mi) 64 | } 65 | } 66 | return mw 67 | } 68 | -------------------------------------------------------------------------------- /web/repo_hook.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "context" 23 | 24 | "github.com/capitalone/checks-out/api" 25 | "github.com/capitalone/checks-out/store" 26 | ) 27 | 28 | func (hook *RepoHook) Process(c context.Context) (interface{}, error) { 29 | return doRepoHook(c, hook) 30 | } 31 | 32 | type RepoOutput struct { 33 | Action string `json:"action"` 34 | Owner string `json:"owner"` 35 | Name string `json:"name"` 36 | } 37 | 38 | func doRepoHook(c context.Context, hook *RepoHook) (*RepoOutput, error) { 39 | 40 | orgDb, err := store.GetOrgName(c, hook.Owner) 41 | if err != nil { 42 | return nil, err 43 | } 44 | user, err := store.GetUser(c, orgDb.UserID) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | repoOutput := &RepoOutput{ 50 | Action: hook.Action, 51 | Owner: hook.Owner, 52 | Name: hook.Name, 53 | } 54 | 55 | switch hook.Action { 56 | case "created": 57 | api.TurnOnRepoQuiet(c, user, hook.Owner, hook.Name, hook.BaseURL) 58 | case "deleted": 59 | repo, err := store.GetRepoOwnerName(c, hook.Owner, hook.Name) 60 | if err != nil { 61 | api.TurnOffRepo(c, user, repo, hook.Owner, hook.Name, hook.BaseURL) 62 | } 63 | default: 64 | repoOutput = nil 65 | } 66 | return repoOutput, nil 67 | } 68 | -------------------------------------------------------------------------------- /web/review_hook.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "context" 23 | 24 | multierror "github.com/mspiegel/go-multierror" 25 | ) 26 | 27 | func (hook *ReviewHook) Process(c context.Context) (interface{}, error) { 28 | approvalOutput, e1 := doApprovalHook(c, &hook.ApprovalHook, hook) 29 | if e1 != nil { 30 | e2 := sendErrorStatus(c, &hook.ApprovalHook, e1) 31 | e1 = multierror.Append(e1, e2) 32 | } 33 | return approvalOutput, e1 34 | } 35 | -------------------------------------------------------------------------------- /web/static/files/angular-toggle-switch.min.js: -------------------------------------------------------------------------------- 1 | !function(){var module=angular.module("toggle-switch",["ng"]);module.provider("toggleSwitchConfig",[function(){this.onLabel="On",this.offLabel="Off",this.knobLabel=" ";var self=this;this.$get=function(){return{onLabel:self.onLabel,offLabel:self.offLabel,knobLabel:self.knobLabel}}}]),module.directive("toggleSwitch",["toggleSwitchConfig",function(toggleSwitchConfig){return{restrict:"EA",replace:!0,require:"ngModel",scope:{disabled:"@",onLabel:"@",offLabel:"@",knobLabel:"@"},template:'',compile:function(element,attrs){return attrs.onLabel||(attrs.onLabel=toggleSwitchConfig.onLabel),attrs.offLabel||(attrs.offLabel=toggleSwitchConfig.offLabel),attrs.knobLabel||(attrs.knobLabel=toggleSwitchConfig.knobLabel),this.link},link:function(scope,element,attrs,ngModelCtrl){var KEY_SPACE=32;element.on("click",function(){scope.$apply(scope.toggle)}),element.on("keydown",function(e){var key=e.which?e.which:e.keyCode;key===KEY_SPACE&&scope.$apply(scope.toggle)}),ngModelCtrl.$formatters.push(function(modelValue){return modelValue}),ngModelCtrl.$parsers.push(function(viewValue){return viewValue}),ngModelCtrl.$viewChangeListeners.push(function(){scope.$eval(attrs.ngChange)}),ngModelCtrl.$render=function(){scope.model=ngModelCtrl.$viewValue},scope.toggle=function(){scope.disabled||(scope.model=!scope.model,ngModelCtrl.$setViewValue(scope.model))}}}}])}(); -------------------------------------------------------------------------------- /web/static/files/checksout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 |
15 |
16 |
17 |
18 | 29 |
30 |
31 |
There are no repositories to manage.
32 |
{{error.data}}
33 |
    34 |
  • 35 |

    {{ repo.slug }} link

    36 | 37 | 38 |
  • 39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 | 55 | 56 |
57 | 66 | -------------------------------------------------------------------------------- /web/static/files/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/web/static/files/favicon.ico -------------------------------------------------------------------------------- /web/static/files/images/maintainers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/web/static/files/images/maintainers.png -------------------------------------------------------------------------------- /web/static/files/images/meowser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/web/static/files/images/meowser.png -------------------------------------------------------------------------------- /web/static/files/images/pending_approval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/web/static/files/images/pending_approval.png -------------------------------------------------------------------------------- /web/static/files/images/received_approval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/checks-out/4cbfb343fb1df0a8622adcd89be9a6f90e2b751f/web/static/files/images/received_approval.png -------------------------------------------------------------------------------- /web/static/files/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 16 | 17 | 18 | 26 | 27 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /web/static/files/toggle_switch.css: -------------------------------------------------------------------------------- 1 | .toggle-switch { 2 | position: absolute; 3 | top: 20px; 4 | right: 20px; 5 | border: 1px solid; 6 | cursor: pointer; 7 | display: inline-block; 8 | text-align: left; 9 | overflow: hidden; 10 | line-height: 8px; 11 | min-width: 75px; 12 | } 13 | 14 | .toggle-switch.disabled > div > span.knob { 15 | background: #AAA; 16 | } 17 | 18 | .toggle-switch span { 19 | cursor: pointer; 20 | display: inline-block; 21 | float: left; 22 | height: 100%; 23 | line-height: 20px; 24 | padding: 4px; 25 | text-align: center; 26 | width: 33%; 27 | white-space: nowrap; 28 | 29 | box-sizing: border-box; 30 | -o-box-sizing: border-box; 31 | -moz-box-sizing: border-box; 32 | -webkit-box-sizing: border-box; 33 | } 34 | 35 | .toggle-switch > div { 36 | position: relative; 37 | width: 150%; 38 | } 39 | 40 | .toggle-switch .knob { 41 | background: red; 42 | border-left: 1px solid #ccc; 43 | border-right: 1px solid #ccc; 44 | background-color: #f5f5f5; 45 | width: 34%; 46 | z-index: 100; 47 | } 48 | 49 | .toggle-switch .switch-on { 50 | left: 0%; 51 | } 52 | 53 | .toggle-switch .switch-off { 54 | left: -50% 55 | } 56 | 57 | .toggle-switch .switch-left, .toggle-switch .switch-right { 58 | z-index: 1; 59 | } 60 | 61 | .toggle-switch .switch-left { 62 | color: #fff; 63 | background: #005fcc; 64 | } 65 | 66 | .toggle-switch .switch-right { 67 | color: #fff; 68 | background: #222222; 69 | } 70 | 71 | .toggle-switch-animate { 72 | transition: left 0.5s; 73 | -o-transition: left 0.5s; 74 | -moz-transition: left 0.5s; 75 | -webkit-transition: left 0.5s; 76 | } 77 | -------------------------------------------------------------------------------- /web/static/static.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package static 20 | 21 | //go:generate go-bindata -pkg static -o static_gen.go files/... 22 | 23 | import ( 24 | "net/http" 25 | 26 | "github.com/elazarl/go-bindata-assetfs" 27 | ) 28 | 29 | func FileSystem() http.FileSystem { 30 | fs := &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "files"} 31 | return &binaryFileSystem{ 32 | fs, 33 | } 34 | } 35 | 36 | type binaryFileSystem struct { 37 | fs http.FileSystem 38 | } 39 | 40 | func (b *binaryFileSystem) Open(name string) (http.File, error) { 41 | return b.fs.Open(name[1:]) 42 | } 43 | -------------------------------------------------------------------------------- /web/store.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | 25 | "github.com/capitalone/checks-out/exterror" 26 | "github.com/capitalone/checks-out/model" 27 | "github.com/capitalone/checks-out/remote" 28 | "github.com/capitalone/checks-out/snapshot" 29 | "github.com/capitalone/checks-out/store" 30 | ) 31 | 32 | func GetHookParameters(c context.Context, h HookCommon, slug string) (HookParams, error) { 33 | result, err := GetHookParametersBasic(c, slug) 34 | if err != nil { 35 | return HookParams{}, err 36 | } 37 | err = snapshot.FixSlackTargets(c, result.Config, result.User.Login) 38 | if err != nil { 39 | return HookParams{}, err 40 | } 41 | result.Event = h.Event 42 | result.Action = h.Action 43 | return result, nil 44 | } 45 | 46 | func GetHookParametersBasic(c context.Context, slug string) (HookParams, error) { 47 | repo, user, cap, err := GetRepoAndUser(c, slug) 48 | if err != nil { 49 | return HookParams{}, err 50 | } 51 | config, maintainer, err := snapshot.GetConfigAndMaintainers(c, user, cap, repo) 52 | if err != nil { 53 | return HookParams{}, err 54 | } 55 | result := HookParams{ 56 | Repo: repo, 57 | User: user, 58 | Cap: cap, 59 | Config: config, 60 | Snapshot: maintainer, 61 | } 62 | return result, nil 63 | } 64 | 65 | func GetRepoAndUser(c context.Context, slug string) (*model.Repo, *model.User, *model.Capabilities, error) { 66 | repo, err := store.GetRepoSlug(c, slug) 67 | if err != nil { 68 | msg := fmt.Sprintf("Error getting repository %s", slug) 69 | err = exterror.Append(err, msg) 70 | return nil, nil, nil, err 71 | } 72 | user, err := store.GetUser(c, repo.UserID) 73 | if err != nil { 74 | msg := fmt.Sprintf("Error getting repository owner %s", repo.Slug) 75 | err = exterror.Append(err, msg) 76 | return nil, nil, nil, err 77 | } 78 | cap, err := remote.Capabilities(c, user) 79 | if err != nil { 80 | msg := fmt.Sprintf("Error getting repository %s", slug) 81 | err = exterror.Append(err, msg) 82 | return nil, nil, nil, err 83 | } 84 | return repo, user, cap, err 85 | } 86 | -------------------------------------------------------------------------------- /web/template/files/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | {{ if eq .error "no_access_error" }} 17 | You do not have permission to create an account. 18 | {{ else if eq .error "oauth_error" }} 19 | We are unable to authenticate you. 20 | {{ else if eq .error "internal_error" }} 21 | An internal error has occurred. Please try again later. 22 | {{else}} 23 | {{ .error }} 24 | {{ end}} 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /web/template/files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /web/template/files/logout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
You were successfully logged out
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /web/template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | //go:generate go-bindata -pkg template -o template_gen.go files/ 4 | 5 | import ( 6 | "encoding/json" 7 | "html/template" 8 | "path/filepath" 9 | ) 10 | 11 | func Template() *template.Template { 12 | funcs := map[string]interface{}{ 13 | "json": marshal, 14 | } 15 | 16 | dir, _ := AssetDir("files") 17 | tmpl := template.New("_") 18 | tmpl.Funcs(funcs) 19 | 20 | for _, name := range dir { 21 | path := filepath.Join("files", name) 22 | src := MustAsset(path) 23 | tmpl = template.Must( 24 | tmpl.New(name).Parse(string(src)), 25 | ) 26 | } 27 | 28 | return tmpl 29 | } 30 | 31 | // marshal is a helper function to render data as JSON 32 | // inside the tempalte. 33 | func marshal(v interface{}) template.JS { 34 | a, _ := json.Marshal(v) 35 | return template.JS(a) 36 | } 37 | -------------------------------------------------------------------------------- /web/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 4 | SPDX-License-Identifier: Apache-2.0 5 | Copyright 2017 Capital One Services, LLC 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and limitations under the License. 17 | 18 | */ 19 | package web 20 | 21 | import ( 22 | "github.com/capitalone/checks-out/version" 23 | 24 | "github.com/gin-gonic/gin" 25 | ) 26 | 27 | // Version returns a response body 28 | // with the version number of the service. 29 | func Version(c *gin.Context) { 30 | c.String(200, version.Version) 31 | } 32 | --------------------------------------------------------------------------------