octokitted 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f1a1eec4292305ffb36ee14107f171a44eb4c4ec36b681ac1cbb43239fee2ca
4
- data.tar.gz: f11e7f4e512223b975247f83720d6d9a465f5126e70e8928007c8be2e01fb350
3
+ metadata.gz: 930bcddc8ee154391c8deb09e7a2c0e483e49d6cb3091adeb70239d0388d3e5d
4
+ data.tar.gz: b64e1ba7b33e4c2eeb73975825bf5fa7cd2eaf37f63383b01427e4adddbd7e4a
5
5
  SHA512:
6
- metadata.gz: 520b1c0f16cb7ff369e287eeebeb8c27efbca078ca31fc16825682dd4bc4cd30594532c63eaf76787af5245092fa5a092a7c71f1e0faa07ea1efc48503ec973a
7
- data.tar.gz: f2b6bf07d1a3463b960daaa02aded2db981bb0640affb5b87afc78a3c7726040f907e915bb3e3c2796c404b35ec9c3d08c666e6347c8ba54dde612eaa7ec3014
6
+ metadata.gz: c2738d3e5a2bb9bb665a8ed84f76ecbe4594b753e46b27a49d504587fbceae86f4a5905bd704368373bf4868551fe5a014716555e274f22fcad95b8d29eb107c
7
+ data.tar.gz: 486cf3f74dc41658968876d79d11383c1d8bfe7577f822c4fe17660555e96f4fa5480f180acd86bd140cbd8af7c972ad4068bb74436a8d2127238ba56141afb9
data/README.md CHANGED
@@ -3,3 +3,11 @@
3
3
  [![test](https://github.com/GrantBirki/octokitted/actions/workflows/test.yml/badge.svg)](https://github.com/GrantBirki/octokitted/actions/workflows/test.yml) [![lint](https://github.com/GrantBirki/octokitted/actions/workflows/lint.yml/badge.svg)](https://github.com/GrantBirki/octokitted/actions/workflows/lint.yml) [![CodeQL](https://github.com/GrantBirki/octokitted/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/GrantBirki/octokitted/actions/workflows/codeql-analysis.yml)
4
4
 
5
5
  A self-hydrating version of Octokit for usage in CI systems - like GitHub Actions!
6
+
7
+ > **kit** or **kitted** (_verb_)
8
+ >
9
+ > Defintion: provide someone or something with the appropriate clothing or equipment.
10
+ >
11
+ > "we were all kitted out in life jackets", "our octokit client was kitted out for CI usage"
12
+
13
+ ![octokitted](./docs/assets/dalle.png)
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "contracts"
4
+
5
+ class Issue
6
+ include Contracts::Core
7
+ include Contracts::Builtin
8
+
9
+ # A helper class for common operations on GitHub Issues
10
+ def initialize(octokitted)
11
+ @octokit = octokitted.octokit
12
+ @log = octokitted.log
13
+ @octokitted = octokitted
14
+ end
15
+
16
+ # Adds a set of labels to an issue or pull request
17
+ # :param labels: The labels to add to the issue (Array of strings)
18
+ # :param issue_number: The issue number to add labels to
19
+ Contract KeywordArgs[labels: ArrayOf[String], issue_number: Maybe[Numeric]] => Any
20
+ def add_labels(labels:, issue_number: nil)
21
+ issue_number = construct_issue_numer(issue_number)
22
+ @log.debug("adding labels: #{labels} to issue: #{issue_number}")
23
+
24
+ @octokit.add_labels_to_an_issue(@octokitted.org_and_repo, issue_number, labels)
25
+ end
26
+
27
+ # Removes a set of labels from an issue or pull request
28
+ # If the label does not exist, the exception is caught and logged
29
+ # :param labels: The labels to remove from the issue (Array of strings)
30
+ # :param issue_number: The issue number to remove labels from
31
+ Contract KeywordArgs[labels: ArrayOf[String], issue_number: Maybe[Numeric]] => Any
32
+ def remove_labels(labels:, issue_number: nil)
33
+ issue_number = construct_issue_numer(issue_number)
34
+ @log.debug("removing labels: #{labels} from issue: #{issue_number}")
35
+
36
+ labels.each do |label|
37
+ @octokit.remove_label(@octokitted.org_and_repo, issue_number, label)
38
+ rescue Octokit::NotFound
39
+ @log.warn("label: #{label} not found on issue: #{issue_number}")
40
+ end
41
+ end
42
+
43
+ # Adds a comment to an issue or pull request
44
+ # :param comment: The comment to add to the issue (String)
45
+ # :param issue_number: The issue number to add the comment to
46
+ Contract KeywordArgs[comment: String, issue_number: Maybe[Numeric]] => Any
47
+ def add_comment(comment:, issue_number: nil)
48
+ issue_number = construct_issue_numer(issue_number)
49
+ @log.debug("adding comment: #{comment} to issue: #{issue_number}")
50
+
51
+ @octokit.add_comment(@octokitted.org_and_repo, issue_number, comment)
52
+ end
53
+
54
+ # Closes an issue
55
+ # :param issue_number: The issue number to close
56
+ Contract KeywordArgs[issue_number: Maybe[Numeric], options: Maybe[Hash]] => Any
57
+ def close(issue_number: nil, options: {})
58
+ issue_number = construct_issue_numer(issue_number)
59
+ @log.debug("closing issue: #{issue_number}")
60
+
61
+ @octokit.close_issue(@octokitted.org_and_repo, issue_number, options)
62
+ end
63
+
64
+ private
65
+
66
+ # Helper method to construct the issue number from the auto-hydrated issue_number if it exists
67
+ # :param issue_number: The issue number to use if not nil
68
+ # :return: The issue number to use
69
+ # Note: If the issue_number is nil, we trye use the auto-hydrated issue_number...
70
+ # ... if the issue_number is not nil, we use that
71
+ Contract Maybe[Numeric] => Numeric
72
+ def construct_issue_numer(issue_number)
73
+ return @octokitted.issue_number if issue_number.nil?
74
+
75
+ return issue_number
76
+ end
77
+ end
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "git"
4
+ require "contracts"
4
5
 
5
6
  class GitPlugin
6
7
  attr_reader :login
7
8
 
9
+ include Contracts::Core
10
+ include Contracts::Builtin
11
+
8
12
  # Initialize the class
13
+ Contract KeywordArgs[logger: Any, login: Maybe[String], token: Maybe[String]] => Any
9
14
  def initialize(logger:, login:, token:)
10
15
  @log = logger
11
16
  @login = login
@@ -14,25 +19,32 @@ class GitPlugin
14
19
 
15
20
  # Removes / cleans up all repos that this class has cloned
16
21
  # :param cloned_repos: An array of paths to cloned repos to remove
22
+ # :return: true to indicate success
23
+ Contract ArrayOf[String] => true
17
24
  def remove_all_clones!(cloned_repos)
18
25
  @log.debug("removing all cloned repos")
19
26
  cloned_repos.each do |path|
20
27
  @log.debug("removing cloned repo: #{path}")
21
28
  FileUtils.rm_r(path)
22
29
  end
30
+ true
23
31
  end
24
32
 
25
33
  # Removes a single cloned repo
26
34
  # :param path: The path to the cloned repo to remove (String)
35
+ # :return: true to indicate success
36
+ Contract String => true
27
37
  def remove_clone!(path)
28
38
  @log.debug("removing cloned repo: #{path}")
29
39
  FileUtils.rm_r(path)
40
+ return true
30
41
  end
31
42
 
32
43
  # Clone a repository
33
44
  # :param path: The relative path to clone the repo to - (default: ".")
34
45
  # :param options: The options to pass to the Git.clone method (default: {} - https://rubydoc.info/gems/git/Git#clone-class_method)
35
46
  # :return: Hash of the Git Object, and the path to the cloned repo
47
+ Contract KeywordArgs[org: String, repo: String, path: Maybe[String], options: Maybe[Hash]] => Hash
36
48
  def clone(org:, repo:, path: ".", options: {})
37
49
  @log.debug("cloning #{org}/#{repo}")
38
50
  git_object = Git.clone("https://#{@token}@github.com/#{org}/#{repo}.git", repo, path:, log: @log, **options)
data/lib/octokitted.rb CHANGED
@@ -2,33 +2,59 @@
2
2
 
3
3
  require "octokit"
4
4
  require "logger"
5
+ require "contracts"
5
6
 
6
7
  require_relative "octokitted/git_plugin"
8
+ require_relative "octokitted/common/issue"
7
9
 
8
10
  class Octokitted
9
11
  # A Octokitted class to interact with the GitHub API
10
- attr_reader :login, :org, :repo, :org_and_repo, :octokit, :cloned_repos
12
+ attr_reader :login,
13
+ :org,
14
+ :repo,
15
+ :org_and_repo,
16
+ :octokit,
17
+ :cloned_repos,
18
+ :log,
19
+ :github_event,
20
+ :sha,
21
+ :issue_number,
22
+ :issue
23
+
24
+ include Contracts::Core
25
+ include Contracts::Builtin
11
26
 
12
27
  # Initialize the class
28
+ # :param event_path: The path to the GitHub event data (defaults to the GITHUB_EVENT_PATH env var)
13
29
  # :param login: The login to use for GitHubAPI interactions (defaults to the owner of the token)
14
30
  # :param org: The org to use with the Octokitted class
15
31
  # :param repo: The repo to interact with with the Octokitted class
32
+ # :param issue_number: The issue/pull_request number to interact with with the Octokitted class
16
33
  # :param token: The token to use to authenticate with the GitHub API
17
34
  # :param logger: The logger to use for logging
18
- def initialize(login: nil, org: nil, repo: nil, token: nil, logger: nil)
35
+ #
36
+ # Note: If you do not provide an org, repo, token, or issue_number, Octokitted will attempt to self-hydrate...
37
+ # ... these values from the environment and the GitHub event data when you call `.new` on the class
38
+ def initialize(event_path: nil, login: nil, org: nil, repo: nil, issue_number: nil, token: nil, logger: nil)
39
+ @log = logger || setup_logger
19
40
  @cloned_repos = []
41
+ @event_path = event_path || ENV.fetch("GITHUB_EVENT_PATH", nil)
42
+ @sha = ENV.fetch("GITHUB_SHA", nil)
20
43
  org_and_repo_hash = fetch_org_and_repo
21
44
  @login = login
22
45
  @org = org || org_and_repo_hash[:org]
23
46
  @repo = repo || org_and_repo_hash[:repo]
24
47
  @token = token || fetch_token
25
48
  @octokit = setup_octokit_client
26
- @log = logger || setup_logger
27
49
  @org_and_repo = org_and_repo_hash[:org_and_repo]
50
+ @github_event = fetch_github_event(@event_path)
51
+ @issue_number = issue_number || fetch_issue_number(@github_event)
28
52
  @login = @octokit.login if @login.nil? # reset the login to the owner of the token if not provided
29
53
 
30
54
  # setup the git plugin
31
55
  @git = GitPlugin.new(logger: @log, login: @login, token: @token)
56
+ # setup the common Issue plugin
57
+ @issue = Issue.new(self)
32
58
 
33
59
  @log.debug("Octokitted initialized")
34
60
  @log.debug("login: #{@octokit.login}")
@@ -38,8 +64,9 @@ class Octokitted
38
64
 
39
65
  # Setter method for the repo instance variable
40
66
  # :param repo: The repo to set
41
- # :return: the new org/repo
67
+ # :return: it does not return as it is a setter method
42
68
  # Example: gh.repo = "test"
69
+ Contract String => Any
43
70
  def repo=(repo)
44
71
  @repo = repo
45
72
  @org_and_repo = "#{@org}/#{@repo}"
@@ -48,8 +75,9 @@ class Octokitted
48
75
 
49
76
  # Setter method for the org instance variable
50
77
  # :param org: The org to set
51
- # :return: the new org/repo
78
+ # :return: it does not return as it is a setter method
52
79
  # Example: gh.org = "test"
80
+ Contract String => Any
53
81
  def org=(org)
54
82
  @org = org
55
83
  @org_and_repo = "#{@org}/#{@repo}"
@@ -60,12 +88,17 @@ class Octokitted
60
88
  # :param path: The relative path to clone the repo to - (default: ".")
61
89
  # :param options: The options to pass (default: {} - https://rubydoc.info/gems/git/Git#clone-class_method)
62
90
  # :return: The Git object to operate with
91
+ Contract Maybe[String], Maybe[Hash] => Git::Base
63
92
  def clone(path: ".", options: {})
64
93
  result = @git.clone(org: @org, repo: @repo, path:, options:)
65
94
  @cloned_repos << result[:path]
66
95
  return result[:git_object]
67
96
  end
68
97
 
98
+ # Remove a cloned repository
99
+ # :param path: The relative path to the cloned repo to remove (String)
100
+ # :return: true to indicate success
101
+ Contract String => true
69
102
  def remove_clone!(path)
70
103
  valid = false
71
104
 
@@ -82,11 +115,16 @@ class Octokitted
82
115
 
83
116
  @git.remove_clone!(path)
84
117
  @cloned_repos.delete(path)
118
+ true
85
119
  end
86
120
 
121
+ # Remove all cloned repositories that have been cloned with this instance of Octokitted
122
+ # :return: true to indicate success
123
+ Contract None => true
87
124
  def remove_all_clones!
88
125
  @git.remove_all_clones!(@cloned_repos)
89
126
  @cloned_repos = []
127
+ true
90
128
  end
91
129
 
92
130
  private
@@ -99,6 +137,7 @@ class Octokitted
99
137
 
100
138
  # Fetch the org and repo from the environment
101
139
  # :return: A hash containing the org and repo, and the org and repo separately
140
+ Contract None => HashOf[Symbol, String]
102
141
  def fetch_org_and_repo
103
142
  org_and_repo = ENV.fetch("GITHUB_REPOSITORY", nil)
104
143
  org = nil
@@ -110,8 +149,41 @@ class Octokitted
110
149
  return { org_and_repo:, org:, repo: }
111
150
  end
112
151
 
152
+ # A helper method that attempts to self-hydrate context from the GitHub event data
153
+ # In Actions, the GITHUB_EVENT_PATH env var is set to a file containing the GitHub json event data
154
+ # If it exists, we try to load it into this class
155
+ # :param event_path: The path to the GitHub event data (defaults to the GITHUB_EVENT_PATH env var)
156
+ # :return: A Hash of the GitHub event data or nil if not found
157
+ Contract Maybe[String] => Maybe[Hash]
158
+ def fetch_github_event(event_path)
159
+ unless event_path
160
+ @log.warn("GITHUB_EVENT_PATH env var not found")
161
+ return nil
162
+ end
163
+
164
+ @log.info("GitHub Event data auto-hydrated")
165
+ return JSON.parse(File.read(event_path), symbolize_names: true)
166
+ end
167
+
168
+ # A helper method that attempts to self-hydrate the issue_number from the GitHub event data
169
+ # :param github_event: The GitHub event data (Hash)
170
+ # :return: The issue_number or nil if not found
171
+ Contract Maybe[Hash] => Maybe[Numeric]
172
+ def fetch_issue_number(github_event)
173
+ if github_event.nil?
174
+ @log.debug("GitHub event data not found - issue_number not auto-hydrated")
175
+ return nil
176
+ end
177
+
178
+ issue_number = (github_event[:issue] || github_event[:pull_request] || github_event)[:number]
179
+
180
+ @log.info("issue_number auto-hydrated - issue_number: #{issue_number}")
181
+ return issue_number
182
+ end
183
+
113
184
  # fetch the GitHub token from the environment
114
- # :return: The GitHub token
185
+ # :return: The GitHub token or nil if not found
186
+ Contract None => Maybe[String]
115
187
  def fetch_token
116
188
  # first try to use the OCTOKIT_ACCESS_TOKEN env var if it exists
117
189
  token = ENV.fetch("OCTOKIT_ACCESS_TOKEN", nil) # if running in actions
@@ -121,7 +193,10 @@ class Octokitted
121
193
  token = ENV.fetch("GITHUB_TOKEN", nil) # if running in actions
122
194
  return token unless token.nil?
123
195
 
124
- raise "No GitHub token found"
196
+ # if we get here, we don't have a token - this is okay because we can still do some things...
197
+ # ... without a token, rate limiting can be an issue
198
+ @log.warn("No GitHub token found")
199
+ return nil
125
200
  end
126
201
 
127
202
  # Setup an Octokit client
data/lib/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Octokitted
4
4
  module Version
5
- VERSION = "0.0.5"
5
+ VERSION = "0.0.7"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octokitted
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grant Birkinbine
@@ -78,6 +78,7 @@ files:
78
78
  - LICENSE
79
79
  - README.md
80
80
  - lib/octokitted.rb
81
+ - lib/octokitted/common/issue.rb
81
82
  - lib/octokitted/git_plugin.rb
82
83
  - lib/version.rb
83
84
  - octokitted.gemspec