Skip to content

Commit 66f2436

Browse files
feat(github_hooks): handle service account github repo host account (#519)
## 📝 Description Service accounts do not have `github_repo_host_account` hence we use their semaphore credentials when injecting a workflow request (either manual task run or manual workflow trigger via API) ## ✅ Checklist - [x] I have tested this change - [ ] This change requires documentation update
1 parent e805920 commit 66f2436

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

github_hooks/app/models/user.rb

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ def self.find_by_provider_login(login, provider)
66
end
77

88
def github_repo_host_account
9-
repo_host_account(::Repository::GITHUB_PROVIDER)
9+
account = repo_host_account(::Repository::GITHUB_PROVIDER)
10+
return account if account.present?
11+
return synthetic_repo_host_account if service_account?
12+
13+
nil
1014
end
1115

1216
def bitbucket_repo_host_account
@@ -16,4 +20,38 @@ def bitbucket_repo_host_account
1620
def repo_host_account(repo_host)
1721
repo_host_accounts.find_by_repo_host(repo_host)
1822
end
23+
24+
def service_account?
25+
creation_source == "service_account"
26+
end
27+
28+
private
29+
30+
def synthetic_repo_host_account
31+
@synthetic_repo_host_account ||= SyntheticRepoHostAccount.new(self)
32+
end
33+
34+
class SyntheticRepoHostAccount
35+
attr_reader :user
36+
37+
def initialize(user)
38+
@user = user
39+
end
40+
41+
def name
42+
user.name || "Service Account"
43+
end
44+
45+
def github_uid
46+
"service_account_#{user.id}".hash.abs.to_s
47+
end
48+
49+
def login
50+
"service-account"
51+
end
52+
53+
def repo_host
54+
::Repository::GITHUB_PROVIDER
55+
end
56+
end
1957
end

github_hooks/spec/models/user_spec.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,92 @@
22

33
RSpec.describe User, :type => :model do
44
it { is_expected.to have_many(:repo_host_accounts).dependent(:destroy) }
5+
6+
describe "#service_account?" do
7+
context "when creation_source is 'service_account'" do
8+
let(:user) { FactoryBot.build(:user, creation_source: "service_account") }
9+
10+
it "returns true" do
11+
expect(user.service_account?).to be_truthy
12+
end
13+
end
14+
15+
context "when creation_source is not 'service_account'" do
16+
let(:user) { FactoryBot.build(:user, creation_source: "github") }
17+
18+
it "returns false" do
19+
expect(user.service_account?).to be_falsey
20+
end
21+
end
22+
23+
context "when creation_source is nil" do
24+
let(:user) { FactoryBot.build(:user, creation_source: nil) }
25+
26+
it "returns false" do
27+
expect(user.service_account?).to be_falsey
28+
end
29+
end
30+
end
31+
32+
describe "#github_repo_host_account" do
33+
context "when user is a regular user with github connection" do
34+
let(:user) { FactoryBot.create(:user, :github_connection) }
35+
36+
it "returns the actual RepoHostAccount" do
37+
account = user.github_repo_host_account
38+
expect(account).to be_a(RepoHostAccount)
39+
expect(account.repo_host).to eq(::Repository::GITHUB_PROVIDER)
40+
end
41+
end
42+
43+
context "when user is a regular user without github connection" do
44+
let(:user) { FactoryBot.create(:user) }
45+
46+
it "returns nil" do
47+
expect(user.github_repo_host_account).to be_nil
48+
end
49+
end
50+
51+
context "when user is a service account" do
52+
let(:user) { FactoryBot.create(:user, creation_source: "service_account", name: "Test Service Account") }
53+
54+
it "returns a synthetic account object" do
55+
account = user.github_repo_host_account
56+
expect(account).to be_a(User::SyntheticRepoHostAccount)
57+
end
58+
59+
it "provides expected name from user.name" do
60+
account = user.github_repo_host_account
61+
expect(account.name).to eq("Test Service Account")
62+
end
63+
64+
it "provides 'Service Account' fallback when name is nil" do
65+
user.update!(name: nil)
66+
account = user.github_repo_host_account
67+
expect(account.name).to eq("Service Account")
68+
end
69+
70+
it "provides a deterministic github_uid based on user id" do
71+
account = user.github_repo_host_account
72+
expected_uid = "service_account_#{user.id}".hash.abs.to_s
73+
expect(account.github_uid).to eq(expected_uid)
74+
end
75+
76+
it "provides 'service-account' login" do
77+
account = user.github_repo_host_account
78+
expect(account.login).to eq("service-account")
79+
end
80+
81+
it "provides correct repo_host" do
82+
account = user.github_repo_host_account
83+
expect(account.repo_host).to eq(::Repository::GITHUB_PROVIDER)
84+
end
85+
86+
it "caches the synthetic account object" do
87+
account1 = user.github_repo_host_account
88+
account2 = user.github_repo_host_account
89+
expect(account1).to be(account2)
90+
end
91+
end
92+
end
593
end

0 commit comments

Comments
 (0)