Skip to content

Commit 265ab43

Browse files
Merge pull request #5 from nullinside-development-group/feature/AssociatingIssuesAndRulesets
Feature/associating issues and rulesets
2 parents c695b97 + 9365a8d commit 265ab43

File tree

11 files changed

+619
-4
lines changed

11 files changed

+619
-4
lines changed

.github/CODEOWNERS

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
* @ProgrammingByPermutation
7+
8+
# Specifically protect the github folder since it controls
9+
# our code owners as well as our github actions.
10+
/.github/ @ProgrammingByPermutation

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "src/octokit.graphql.net"]
2+
path = src/octokit.graphql.net
3+
url = https://github.com/ProgrammingByPermutation/octokit.graphql.net.git

src/Nullinside.Cicd.GitHub.sln

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{13
1212
..\README.md = ..\README.md
1313
EndProjectSection
1414
EndProject
15+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit.GraphQL", "octokit.graphql.net\src\Octokit.GraphQL\Octokit.GraphQL.csproj", "{A6ABB06A-166F-41B5-BC79-10B3068A6862}"
16+
EndProject
17+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit.GraphQL.Core", "octokit.graphql.net\src\Octokit.GraphQL.Core\Octokit.GraphQL.Core.csproj", "{33A1CD7F-ACA4-4942-A405-1CB9DB6ABB25}"
18+
EndProject
19+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit.GraphQL.Core.Generation", "octokit.graphql.net\src\Octokit.GraphQL.Core.Generation\Octokit.GraphQL.Core.Generation.csproj", "{98062DEE-C09A-4479-A4D9-A19E2EA4F0E8}"
20+
EndProject
1521
Global
1622
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1723
Debug|Any CPU = Debug|Any CPU
@@ -22,5 +28,17 @@ Global
2228
{94D9EEEF-B6C7-46AE-AB78-562D42E719A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
2329
{94D9EEEF-B6C7-46AE-AB78-562D42E719A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
2430
{94D9EEEF-B6C7-46AE-AB78-562D42E719A2}.Release|Any CPU.Build.0 = Release|Any CPU
31+
{A6ABB06A-166F-41B5-BC79-10B3068A6862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32+
{A6ABB06A-166F-41B5-BC79-10B3068A6862}.Debug|Any CPU.Build.0 = Debug|Any CPU
33+
{A6ABB06A-166F-41B5-BC79-10B3068A6862}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{A6ABB06A-166F-41B5-BC79-10B3068A6862}.Release|Any CPU.Build.0 = Release|Any CPU
35+
{33A1CD7F-ACA4-4942-A405-1CB9DB6ABB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36+
{33A1CD7F-ACA4-4942-A405-1CB9DB6ABB25}.Debug|Any CPU.Build.0 = Debug|Any CPU
37+
{33A1CD7F-ACA4-4942-A405-1CB9DB6ABB25}.Release|Any CPU.ActiveCfg = Release|Any CPU
38+
{33A1CD7F-ACA4-4942-A405-1CB9DB6ABB25}.Release|Any CPU.Build.0 = Release|Any CPU
39+
{98062DEE-C09A-4479-A4D9-A19E2EA4F0E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40+
{98062DEE-C09A-4479-A4D9-A19E2EA4F0E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
41+
{98062DEE-C09A-4479-A4D9-A19E2EA4F0E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
42+
{98062DEE-C09A-4479-A4D9-A19E2EA4F0E8}.Release|Any CPU.Build.0 = Release|Any CPU
2543
EndGlobalSection
2644
EndGlobal

src/Nullinside.Cicd.GitHub/.editorconfig

Lines changed: 364 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Nullinside.Cicd.GitHub;
2+
3+
public static class Constants {
4+
public const string GITHUB_ORG = "nullinside-development-group";
5+
public const int GITHUB_PROJECT_NUM = 1;
6+
}

src/Nullinside.Cicd.GitHub/Nullinside.Cicd.GitHub.csproj

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,21 @@
99
</PropertyGroup>
1010

1111
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
12-
<DocumentationFile>bin\Debug\Nullinside.Cicd.GitHub.xml</DocumentationFile>
12+
<DocumentationFile>bin\Debug\Nullinside.Cicd.GitHub.xml</DocumentationFile>
1313
</PropertyGroup>
1414

1515
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
16-
<DocumentationFile>bin\Release\Nullinside.Cicd.GitHub.xml</DocumentationFile>
17-
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
16+
<DocumentationFile>bin\Release\Nullinside.Cicd.GitHub.xml</DocumentationFile>
17+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
1818
</PropertyGroup>
1919

20+
<ItemGroup>
21+
<PackageReference Include="log4net" Version="2.0.17"/>
22+
<PackageReference Include="Octokit" Version="13.0.1" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<ProjectReference Include="..\octokit.graphql.net\src\Octokit.GraphQL\Octokit.GraphQL.csproj" />
27+
</ItemGroup>
28+
2029
</Project>
Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
11
// See https://aka.ms/new-console-template for more information
22

3-
Console.WriteLine("Hello, World!");
3+
using Nullinside.Cicd.GitHub;
4+
using Nullinside.Cicd.GitHub.Rule;
5+
6+
using Octokit;
7+
using Octokit.GraphQL;
8+
9+
using Connection = Octokit.GraphQL.Connection;
10+
using ID = Octokit.GraphQL.ID;
11+
using ProductHeaderValue = Octokit.ProductHeaderValue;
12+
using Query = Octokit.GraphQL.Query;
13+
14+
IRepoRule?[] rules = AppDomain.CurrentDomain.GetAssemblies()
15+
.SelectMany(a => a.GetTypes())
16+
.Where(t => typeof(IRepoRule).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface)
17+
.Select(t => Activator.CreateInstance(t) as IRepoRule)
18+
.Where(o => null != o)
19+
.ToArray();
20+
21+
var client = new GitHubClient(new ProductHeaderValue("nullinside")) {
22+
Credentials = new Credentials(Environment.GetEnvironmentVariable("GITHUB_PAT"))
23+
};
24+
25+
var graphQl = new Connection(new Octokit.GraphQL.ProductHeaderValue("nullinside"),
26+
Environment.GetEnvironmentVariable("GITHUB_PAT"));
27+
28+
ID projectId = await graphQl.Run(new Query()
29+
.Organization(Constants.GITHUB_ORG)
30+
.ProjectV2(Constants.GITHUB_PROJECT_NUM)
31+
.Select(p => p.Id));
32+
33+
IReadOnlyList<Repository>? repository = await client.Repository.GetAllForOrg(Constants.GITHUB_ORG);
34+
foreach (Repository repo in repository) {
35+
if (repo.Private) {
36+
continue;
37+
}
38+
39+
foreach (IRepoRule? rule in rules) {
40+
await rule.Handle(client, graphQl, projectId, repo);
41+
}
42+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Octokit;
2+
using Octokit.GraphQL;
3+
using Octokit.GraphQL.Core;
4+
using Octokit.GraphQL.Model;
5+
6+
using Connection = Octokit.GraphQL.Connection;
7+
using Repository = Octokit.Repository;
8+
9+
namespace Nullinside.Cicd.GitHub.Rule;
10+
11+
public class AssociateIssuesWithProject : IRepoRule {
12+
public async Task Handle(GitHubClient client, Connection graphQl, ID projectId, Repository repo) {
13+
if (!repo.HasIssues) {
14+
return;
15+
}
16+
17+
var issues = await graphQl.Run(new Query()
18+
.Repository(repo.Name, Constants.GITHUB_ORG)
19+
.Issues()
20+
.AllPages()
21+
.Select(i => new {
22+
i.Id,
23+
i.Number,
24+
ProjectItems = i.ProjectItems(null, null, null, null, null)
25+
.AllPages()
26+
.Select(p => p.Id)
27+
.ToList()
28+
}));
29+
30+
foreach (var issue in issues) {
31+
if (issue.ProjectItems.Count > 0) {
32+
continue;
33+
}
34+
35+
Console.WriteLine($"{repo.Name}: Associating issue #{issue.Number}");
36+
await graphQl.Run(new Mutation()
37+
.AddProjectV2ItemById(new Arg<AddProjectV2ItemByIdInput>(new AddProjectV2ItemByIdInput {
38+
ProjectId = projectId,
39+
ContentId = issue.Id
40+
}))
41+
.Select(x => x.Item.Id));
42+
}
43+
}
44+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using Octokit;
2+
using Octokit.GraphQL;
3+
using Octokit.GraphQL.Core;
4+
using Octokit.GraphQL.Model;
5+
6+
using Connection = Octokit.GraphQL.Connection;
7+
using Repository = Octokit.Repository;
8+
9+
namespace Nullinside.Cicd.GitHub.Rule;
10+
11+
public class CreateRulesets : IRepoRule {
12+
public async Task Handle(GitHubClient client, Connection graphQl, ID projectId, Repository repo) {
13+
// This currently doesn't run properly. You get an error about not specifying multiple Parameters on the status
14+
// checks. Waiting on an update from the source library to not include nulls in the compiled query.
15+
// https://github.com/octokit/octokit.graphql.net/issues/320
16+
IEnumerable<string>? rulesets = await graphQl.Run(new Query()
17+
.Repository(repo.Name, Constants.GITHUB_ORG)
18+
.Rulesets()
19+
.AllPages()
20+
.Select(i => i.Name));
21+
22+
string? expectedRuleset =
23+
rulesets.FirstOrDefault(r => "main".Equals(r, StringComparison.InvariantCultureIgnoreCase));
24+
if (null != expectedRuleset) {
25+
return;
26+
}
27+
28+
ID id = await graphQl.Run(new Query()
29+
.Repository(repo.Name, Constants.GITHUB_ORG)
30+
.Select(i => i.Id));
31+
32+
Console.WriteLine($"{repo.Name}: Creating default ruleset");
33+
StatusCheckConfigurationInput[] statusChecks = null;
34+
if ("Typescript".Equals(repo.Language, StringComparison.InvariantCultureIgnoreCase)) {
35+
statusChecks = new[] {
36+
new StatusCheckConfigurationInput {
37+
Context = "ng test"
38+
},
39+
new StatusCheckConfigurationInput {
40+
Context = "ng lint"
41+
},
42+
new StatusCheckConfigurationInput {
43+
Context = "Analyze (javascript-typescript)"
44+
},
45+
new StatusCheckConfigurationInput {
46+
Context = "CodeQL"
47+
}
48+
};
49+
}
50+
else if ("C#".Equals(repo.Language, StringComparison.InvariantCultureIgnoreCase)) {
51+
statusChecks = new[] {
52+
new StatusCheckConfigurationInput {
53+
Context = "Roslyn Compiler Warnings"
54+
},
55+
new StatusCheckConfigurationInput {
56+
Context = "Tests"
57+
},
58+
new StatusCheckConfigurationInput {
59+
Context = "Analyze (csharp)"
60+
},
61+
new StatusCheckConfigurationInput {
62+
Context = "CodeQL"
63+
}
64+
};
65+
}
66+
67+
var rules = new List<RepositoryRuleInput>([
68+
new RepositoryRuleInput {
69+
Type = RepositoryRuleType.Deletion
70+
},
71+
new RepositoryRuleInput {
72+
Type = RepositoryRuleType.PullRequest,
73+
Parameters = new RuleParametersInput {
74+
PullRequest = new PullRequestParametersInput {
75+
DismissStaleReviewsOnPush = true,
76+
RequireCodeOwnerReview = true
77+
}
78+
}
79+
}
80+
]);
81+
82+
if (null != statusChecks) {
83+
rules.Add(new RepositoryRuleInput {
84+
Type = RepositoryRuleType.RequiredStatusChecks,
85+
Parameters = new RuleParametersInput {
86+
RequiredStatusChecks = new RequiredStatusChecksParametersInput {
87+
RequiredStatusChecks = statusChecks,
88+
StrictRequiredStatusChecksPolicy = true
89+
}
90+
}
91+
});
92+
}
93+
94+
await graphQl.Run(new Mutation()
95+
.CreateRepositoryRuleset(new Arg<CreateRepositoryRulesetInput>(new CreateRepositoryRulesetInput {
96+
SourceId = id,
97+
Name = "main",
98+
Enforcement = RuleEnforcement.Active,
99+
Target = RepositoryRulesetTarget.Branch,
100+
Rules = rules,
101+
Conditions = new RepositoryRuleConditionsInput {
102+
RefName = new RefNameConditionTargetInput {
103+
Include = new[] { "~DEFAULT_BRANCH" },
104+
Exclude = new string[] { }
105+
}
106+
}
107+
}))
108+
.Select(x => x.Ruleset.Id)
109+
);
110+
}
111+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Octokit;
2+
using Octokit.GraphQL;
3+
4+
using Connection = Octokit.GraphQL.Connection;
5+
6+
namespace Nullinside.Cicd.GitHub.Rule;
7+
8+
public interface IRepoRule {
9+
Task Handle(GitHubClient client, Connection graphQl, ID projectId, Repository repo);
10+
}

0 commit comments

Comments
 (0)