Skip to content

Commit 8097637

Browse files
Initial commit
0 parents  commit 8097637

File tree

9 files changed

+336
-0
lines changed

9 files changed

+336
-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/tests/test/test_cases.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.planned_values.root_module.resources[] | select(.type=="github_branch_default").values.branch;"develop";default branch
2+
.planned_values.root_module.resources[] | select(.type=="github_repository_collaborator").values.username;"softservedata";collaborator
3+
.planned_values.root_module.resources[] | select(.type=="github_branch_protection").values | select(.pattern == "develop").required_pull_request_reviews[].required_approving_review_count;2;develop protection
4+
.planned_values.root_module.resources[] | select(.type=="github_branch_protection").values | select(.pattern == "main").required_pull_request_reviews[].require_code_owner_reviews;true;main protection
5+
.planned_values.root_module.resources[] | select(.type=="github_actions_secret").values.secret_name;"PAT";actions secret
6+
.planned_values.root_module.resources[] | select(.type=="github_repository_deploy_key").values.title;"DEPLOY_KEY";deploy key

.github/tests/test/tests.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
old_ifs="$IFS"
4+
IFS=";"
5+
exit_code=0
6+
7+
function print() {
8+
echo -e "$3 The configuration '$1' is $2"
9+
}
10+
11+
while read property expected message
12+
do
13+
if [[ $(echo $1 | jq $property) == $expected ]]; then
14+
print $message OK '\033[0;32m'
15+
else
16+
print $message WRONG '\033[0;31m'
17+
exit_code=1
18+
fi
19+
done < $2
20+
21+
IFS=$old_ifs
22+
exit $exit_code

.github/workflows/ruby.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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: Write code to file
35+
run: |
36+
cat << EOTFC > main.tf
37+
${{ secrets.TERRAFORM }}
38+
EOTFC
39+
- name: Setup Terraform
40+
uses: hashicorp/setup-terraform@v2
41+
with:
42+
terraform_wrapper: false
43+
- name: Terraform Init
44+
run: terraform init
45+
- name: Test Terraform Config
46+
run: |
47+
terraform validate
48+
terraform plan -no-color -out tfplan
49+
PLAN=$(terraform show -json tfplan)
50+
bash -e .github/tests/test/tests.sh "$PLAN" .github/tests/test/test_cases.txt
51+
- name: Set up Ruby
52+
uses: ruby/setup-ruby@v1
53+
with:
54+
ruby-version: '3.2'
55+
working-directory: '.github/tests'
56+
bundler-cache: true
57+
- name: Run tests
58+
env:
59+
URL: ${{ github.repository }}
60+
TOKEN: ${{ steps.my-app.outputs.token }}
61+
working-directory: '.github/tests'
62+
run:
63+
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 Terraform Topic
2+
3+
Write Terraform code that configures the GitHub repository according to the following requirements, execute it and save your Terraform code in a repository secret named `TERRAFORM`.
4+
5+
1. The GitHub repository should assign user `softservedata` as a collaborator.
6+
7+
2. A branch named `develop` should be created as default branch.
8+
9+
3. Protect the `main` and `develop` branches according to these rules:
10+
- a user cannot merge into both branches without a pull request
11+
- merging into the `develop` branch is allowed only if there are two approvals
12+
- merging into the `main` branch is allowed only if the owner has approved the pull request
13+
- assign the user `softservedata` as the code owner for all the files in the `main` branch
14+
4. A pull request template (pull_request_template.md) should be added to the `.github` directory to allow users to create an issue in the required format:
15+
16+
## Describe your changes
17+
18+
## Issue ticket number and link
19+
20+
## Checklist before requesting a review
21+
- [ ] I have performed a self-review of my code
22+
- [ ] If it is a core feature, I have added thorough tests
23+
- [ ] Do we need to implement analytics?
24+
- [ ] Will this be part of a product update? If yes, please write one phrase about this update
25+
26+
5. A deploy key named `DEPLOY_KEY` should be added to the repository.
27+
28+
6. Create a Discord server and enable notifications when a pull request is created.
29+
30+
7. For GitHub actions, perform the following:
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 the PAT to the 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)