Skip to content

Commit e326aec

Browse files
Initial commit
0 parents  commit e326aec

File tree

7 files changed

+309
-0
lines changed

7 files changed

+309
-0
lines changed

.github/tests/Gemfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
# gem "rails"

.github/tests/Gemfile.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
5+
PLATFORMS
6+
arm64-darwin-21
7+
x86_64-linux
8+
9+
DEPENDENCIES
10+
11+
BUNDLED WITH
12+
2.3.11

.github/tests/src/script.rb

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'net/http'
2+
require 'uri'
3+
require 'json'
4+
class GithubApi
5+
@@prefix = 'https://api.github.com/repos'
6+
7+
def initialize(repo_uri, token)
8+
@repo_uri = repo_uri
9+
@token = token
10+
end
11+
def get(url)
12+
uri = URI.parse("#{@@prefix}/#{@repo_uri}#{"/#{url}" if url != ''}")
13+
request = Net::HTTP::Get.new(uri)
14+
request["Accept"] = "application/vnd.github+json"
15+
request["Authorization"] = "Bearer #{@token}"
16+
request["X-Github-Api-Version"] = "2022-11-28"
17+
18+
req_options = {
19+
use_ssl: uri.scheme == "https",
20+
}
21+
22+
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
23+
http.request(request)
24+
end
25+
end
26+
27+
def branch_protected?(branch_name)
28+
branches = JSON.parse(get('branches').body).select{|element| element["name"] == branch_name}
29+
return "Branch with #{branch_name} doesn't exist" if branches.empty?
30+
branches[0]["protected"]
31+
end
32+
33+
def branch_exist?(branch_name)
34+
!JSON.parse(get('branches').body).select{|element| element["name"] == branch_name}.empty?
35+
end
36+
37+
def branches
38+
JSON.parse(get('branches').body)
39+
end
40+
41+
def branch(json_obj, branch_name)
42+
JSON.parse(get('branches').body).select{|element| element["name"] == branch_name}
43+
end
44+
45+
def default_branch
46+
JSON.parse(get('').body)["default_branch"]
47+
end
48+
49+
def file_branch(file_name, branch_name)
50+
return nil if get("contents/#{file_name}?ref=#{branch_name}").code != '200'
51+
old_uri = @@prefix
52+
@@prefix = 'https://raw.githubusercontent.com'
53+
result = get("#{branch_name}/#{file_name}").body
54+
@@prefix = old_uri
55+
result
56+
end
57+
58+
def rules_required_pull_request_reviews(branch_name)
59+
response = get("branches/#{branch_name}/protection")
60+
return nil if response.code != '200'
61+
JSON.parse(response.body)["required_pull_request_reviews"]
62+
end
63+
64+
def get_branch_ruleset(branch_name)
65+
branch_ruleset = nil
66+
response = get("rulesets")
67+
JSON.parse(response.body).each do |ruleset|
68+
id = ruleset['id']
69+
details = get("rulesets/#{id}")
70+
if JSON.parse(details.body)['conditions']['ref_name']['include'].any? {|elem| elem.include?(branch_name)}
71+
branch_ruleset = JSON.parse(details.body)['rules']
72+
end
73+
end
74+
branch_ruleset
75+
end
76+
77+
def deploy_keys
78+
response = get("keys")
79+
return nil if response.code != '200'
80+
JSON.parse(response.body)
81+
end
82+
83+
end

.github/tests/test/script_test.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
require 'test/unit'
2+
require_relative '../src/script'
3+
4+
class ScriptTest < Test::Unit::TestCase
5+
6+
def setup
7+
url = ENV['URL'].nil? ? '' : ENV["URL"]
8+
token = ENV['TOKEN'].nil? ? '' : ENV["TOKEN"]
9+
@secrets_token = ENV['SECRETS_TOKEN']
10+
@obj = GithubApi.new(url, token)
11+
end
12+
13+
def test_health_check
14+
assert_not_nil(@obj.instance_variable_get('@repo_uri'), 'Url alive')
15+
assert_not_nil(@obj.instance_variable_get('@token'), 'Token alive')
16+
end
17+
18+
def test_token_present
19+
actual = @secrets_token =~ /^ghp_\w{36}$/
20+
assert_not_nil(actual, "Secret with name 'PAT' with valid personal access token doesn't exist")
21+
end
22+
23+
def test_deploy_key_present
24+
response = @obj.deploy_keys
25+
assert_not_nil(response, "Access denied")
26+
deploy_key = response.find {|element| element['title'] == 'DEPLOY_KEY'}
27+
assert_not_nil(deploy_key, "The deploy key with name 'DEPLOY_KEY' doesn't exist")
28+
end
29+
30+
def test_main_present
31+
actual = @obj.branch_exist?('main')
32+
assert(actual, 'Branch main is not present')
33+
end
34+
35+
def test_main_protected
36+
actual = @obj.branch_protected?('main')
37+
assert(actual, 'Branch main is not protected')
38+
end
39+
40+
def test_develop_present
41+
actual = @obj.branch_exist?('develop')
42+
assert(actual, 'Branch develop is not present')
43+
end
44+
45+
def test_develop_protected
46+
actual = @obj.branch_protected?('develop')
47+
assert(actual, 'Branch develop is not protected')
48+
end
49+
50+
def test_develop_default
51+
actual = @obj.default_branch
52+
expected = 'develop'
53+
assert_equal(expected, actual, 'Default branch isn\'t develop')
54+
end
55+
56+
def test_codeowners_contains_user
57+
user_name = 'softservedata'
58+
content = @obj.file_branch('CODEOWNERS', 'main') || @obj.file_branch('.github/CODEOWNERS', 'main') || @obj.file_branch('docs/CODEOWNERS', 'main')
59+
assert_not_nil(content, 'File CODEOWNERS doesn\'t exist on main branch')
60+
assert(content.include?(user_name), "User #{user_name} doesn't present in CODEOWNERS")
61+
end
62+
63+
def test_codeowners_not_present_develop
64+
content = @obj.file_branch('CODEOWNERS', 'develop')
65+
assert_nil(content, 'File CODEOWNERS exist on develop branch')
66+
end
67+
68+
def test_deny_merge_main
69+
classic_rules = @obj.rules_required_pull_request_reviews('main')
70+
rulesets = @obj.get_branch_ruleset('main')
71+
rulesets_rules = rulesets&.find { |rule| rule['type'] == 'pull_request' }
72+
assert_not_nil(classic_rules || rulesets_rules, 'We should not allow merge to main branch without PR')
73+
end
74+
75+
def test_deny_merge_develop
76+
classic_rules = @obj.rules_required_pull_request_reviews('develop')
77+
rulesets = @obj.get_branch_ruleset('develop')
78+
rulesets_rules = rulesets&.find { |rule| rule['type'] == 'pull_request' }
79+
assert_not_nil(classic_rules || rulesets_rules, 'We should not allow merge to develop branch without PR ')
80+
end
81+
82+
def test_2_approvals_develop
83+
classic_required_approving_review_count = @obj.rules_required_pull_request_reviews('develop').nil? || @obj.rules_required_pull_request_reviews('develop')["required_approving_review_count"]
84+
pull_request_rulesets_rules = @obj.get_branch_ruleset('develop')
85+
rulesets_required_approving_review_count = pull_request_rulesets_rules&.find { |rule| rule['type'] == 'pull_request' }&.[]('parameters')&.[]('required_approving_review_count')
86+
expected = 2
87+
required_approving_review_count = classic_required_approving_review_count == expected || rulesets_required_approving_review_count == expected
88+
assert_true(required_approving_review_count, 'We should have 2 approvals before merge to develop branch')
89+
end
90+
91+
def test_without_approval_main
92+
classic_required_approving_review_count = @obj.rules_required_pull_request_reviews('main').nil? || @obj.rules_required_pull_request_reviews('main')["required_approving_review_count"]
93+
pull_request_rulesets_rules = @obj.get_branch_ruleset('main')
94+
rulesets_required_approving_review_count = pull_request_rulesets_rules&.find { |rule| rule['type'] == 'pull_request' }&.[]('parameters')&.[]('required_approving_review_count')
95+
expected = 0
96+
required_approving_review_count = classic_required_approving_review_count == expected || rulesets_required_approving_review_count == expected
97+
assert_true(required_approving_review_count, 'We shouldn\'t have any approvals before merge to main branch')
98+
end
99+
100+
def test_approve_from_user
101+
user_name = 'softservedata'
102+
classic_require_code_owner_review = @obj.rules_required_pull_request_reviews('main')["require_code_owner_reviews"]
103+
pull_request_rulesets_rules = @obj.get_branch_ruleset('main')
104+
rulesets_require_code_owner_review = pull_request_rulesets_rules&.find { |rule| rule['type'] == 'pull_request' }&.[]('parameters')&.[]('require_code_owner_review')
105+
assert(classic_require_code_owner_review || rulesets_require_code_owner_review, "We should not allow merge to main branch without approve from #{user_name}")
106+
end
107+
108+
def test_PR_template_present
109+
actual = @obj.file_branch('.github/pull_request_template.md', 'main')
110+
assert_not_nil(actual, 'Pull request template is absent')
111+
end
112+
113+
end

.github/workflows/ruby.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# This workflow uses actions that are not certified by GitHub.
2+
# They are provided by a third-party and are governed by
3+
# separate terms of service, privacy policy, and support
4+
# documentation.
5+
6+
# GitHub recommends pinning actions to a commit SHA.
7+
# To get a newer version, you will need to update the SHA.
8+
# You can also reference a tag or branch, but the action may change without warning.
9+
10+
name: Ruby
11+
12+
env:
13+
SECRETS_TOKEN: ${{ secrets.PAT }}
14+
15+
on:
16+
push:
17+
branches: [ main, develop ]
18+
pull_request:
19+
branches: [ main, develop ]
20+
workflow_dispatch:
21+
22+
jobs:
23+
test:
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- name: Install my-app token
28+
id: my-app
29+
uses: getsentry/action-github-app-token@v3
30+
with:
31+
app_id: ${{ secrets.APP_ID }}
32+
private_key: ${{ secrets.APP_PRIVATE_KEY }}
33+
- uses: actions/checkout@v5
34+
- name: Add outside users to organization
35+
run: |
36+
users=$(curl -L -s \
37+
-H "Accept: application/vnd.github+json" \
38+
-H "Authorization: Bearer ${{ steps.my-app.outputs.token }}" \
39+
-H "X-GitHub-Api-Version: 2022-11-28" \
40+
https://api.github.com/repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/collaborators?affiliation=outside \
41+
| jq '.[] .id')
42+
for user in $users
43+
do
44+
curl -s -L -o /dev/null \
45+
-X POST \
46+
-H "Accept: application/vnd.github+json" \
47+
-H "Authorization: Bearer ${{ steps.my-app.outputs.token }}" \
48+
-H "X-GitHub-Api-Version: 2022-11-28" \
49+
https://api.github.com/orgs/${{ github.repository_owner }}/invitations \
50+
-d "{\"invitee_id\": $user}"
51+
done
52+
- name: Set up Ruby
53+
uses: ruby/setup-ruby@v1
54+
with:
55+
ruby-version: '3.2'
56+
working-directory: '.github/tests'
57+
bundler-cache: true
58+
- name: Run tests
59+
env:
60+
URL: ${{ github.repository }}
61+
TOKEN: ${{ steps.my-app.outputs.token }}
62+
working-directory: '.github/tests'
63+
run:
64+
ruby test/script_test.rb

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Task on GitHub Topic
2+
3+
1. Add user `softservedata` to this repository.
4+
5+
2. Create branch `develop` as default branch.
6+
7+
3. Protect branches `main` and `develop` with these rules:
8+
- user can't merge to both branches without pull request
9+
- allowed to merge to `develop` branch only if we have 2 approvals
10+
- merge to `main` branch allowed if only owner approved PR
11+
- assign the user `softservedata` as the code owner for all the files in the `main` branch
12+
4. Add template (pull_request_template.md) to `.github` directory for creating issue in format:
13+
14+
## Describe your changes
15+
16+
## Issue ticket number and link
17+
18+
## Checklist before requesting a review
19+
- [ ] I have performed a self-review of my code
20+
- [ ] If it is a core feature, I have added thorough tests
21+
- [ ] Do we need to implement analytics?
22+
- [ ] Will this be part of a product update? If yes, please write one phrase about this update
23+
24+
5. Create project for this repository.
25+
26+
6. Add deploy key with name `DEPLOY_KEY` to your repository.
27+
28+
7. Create discord server and add notification when PR was created.
29+
30+
8. For github actions:
31+
- create PAT (Personal Access Token) with **Full control of private repositories** and **Full control of orgs and teams, read and write org projects**
32+
- add to repository actions secrets key with the name `PAT` and the value of the created PAT

src/.keep

Whitespace-only changes.

0 commit comments

Comments
 (0)