gurney_client 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +10 -8
- data/README.md +30 -21
- data/lib/gurney/api.rb +6 -5
- data/lib/gurney/cli.rb +90 -82
- data/lib/gurney/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e7470d3ea652657be230f28b42b3035ee0e2c47122dc297533428831fc20b2b
|
4
|
+
data.tar.gz: 7e3bf5796d850cc8e600959a348f521d9d7ee10f5f7e329da391c0b125f57db0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c04cfc2febd10dcb6be12b4b8466f5c11d308954c57a7588a0bb41100f64eabc4656867af4d97d1c2d40ee607d5a3206e8fc3c0477021d04efd471c599abd540
|
7
|
+
data.tar.gz: cb327dd15bfd5aea5c30a87ff271555c17c9e29c8ec35379fa9bdb4ce3fd21b935e6ba80d1a0057abdd7f75b76d263da1089b209bce7576c857ca570f0f563e8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Gurney changelog
|
2
2
|
|
3
|
+
## 0.4.0 (2024-11-15)
|
4
|
+
* Added: Reporting of the repository path as identifier. Should it ever change,
|
5
|
+
it is an indicator for an unchanged gurney.yml in a project fork, and the
|
6
|
+
API may respond with an error.
|
7
|
+
|
3
8
|
## 0.3.0 (2024-11-14)
|
4
9
|
* Added: Compatibility with Ruby 3
|
5
10
|
* Fixed: Support UTF-8 chars in branch names
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gurney_client (0.
|
4
|
+
gurney_client (0.4.0)
|
5
5
|
bundler (< 3)
|
6
6
|
colorize (~> 0.8)
|
7
7
|
git (~> 1.5)
|
@@ -10,22 +10,24 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
addressable (2.8.
|
14
|
-
public_suffix (>= 2.0.2, <
|
13
|
+
addressable (2.8.7)
|
14
|
+
public_suffix (>= 2.0.2, < 7.0)
|
15
15
|
byebug (11.0.1)
|
16
16
|
colorize (0.8.1)
|
17
17
|
diff-lcs (1.3)
|
18
|
-
git (1.
|
18
|
+
git (1.19.1)
|
19
19
|
addressable (~> 2.8)
|
20
20
|
rchardet (~> 1.8)
|
21
21
|
httparty (0.17.3)
|
22
22
|
mime-types (~> 3.0)
|
23
23
|
multi_xml (>= 0.5.2)
|
24
|
-
|
24
|
+
logger (1.6.1)
|
25
|
+
mime-types (3.6.0)
|
26
|
+
logger
|
25
27
|
mime-types-data (~> 3.2015)
|
26
|
-
mime-types-data (3.
|
28
|
+
mime-types-data (3.2024.1105)
|
27
29
|
multi_xml (0.6.0)
|
28
|
-
public_suffix (5.
|
30
|
+
public_suffix (5.1.1)
|
29
31
|
rake (13.0.1)
|
30
32
|
rchardet (1.8.0)
|
31
33
|
rspec (3.9.0)
|
@@ -52,4 +54,4 @@ DEPENDENCIES
|
|
52
54
|
rspec
|
53
55
|
|
54
56
|
BUNDLED WITH
|
55
|
-
2.
|
57
|
+
2.3.19
|
data/README.md
CHANGED
@@ -1,30 +1,31 @@
|
|
1
|
-
|
1
|
+
# Gurney
|
2
2
|
|
3
|
-
Gurney is a small tool to extract dependencies from project files and report
|
4
|
-
|
3
|
+
Gurney is a small tool to extract dependencies from project files and report
|
4
|
+
them to a web API. Modes:
|
5
|
+
- normal
|
6
|
+
- local pre-push hook
|
7
|
+
- remote post-receive hook
|
8
|
+
Usually, we configure the latter on our Git server to automatically run on each
|
9
|
+
push, with the API url passed as command line option.
|
5
10
|
|
6
|
-
When run as a
|
7
|
-
|
8
|
-
|
11
|
+
When run as a post-receive hook, Gurney will make a bare copy of the project and
|
12
|
+
look for a gurney.yml file. If present, Gurney looks at the configured branches
|
13
|
+
and collects their dependencies. These are reported to the web API.
|
9
14
|
|
10
|
-
|
15
|
+
## Usage
|
11
16
|
```
|
12
17
|
Usage: gurney [options]
|
13
|
-
--api-url [API URL]
|
14
|
-
|
15
|
-
|
16
|
-
--api-token [API TOKEN]
|
17
|
-
Token to be send to the api in the X-AuthToken header
|
18
|
+
--api-url [API URL] Url for web API call, can have parameters for <project_id> and <branch>
|
19
|
+
Example: --api-url "https://example.com/project/<project_id>/branch/<branch>"
|
20
|
+
--api-token [API TOKEN] Token to be sent to the API in the X-AuthToken header
|
18
21
|
-c, --config [CONFIG FILE] Config file to use
|
19
|
-
-h, --hook Run as a
|
20
|
-
--client-hook
|
21
|
-
|
22
|
-
|
23
|
-
--help
|
24
|
-
Prints this help
|
22
|
+
-h, --hook Run as a Git post-receive hook
|
23
|
+
--client-hook Run as a Git pre-push hook
|
24
|
+
-p, --project-id [PROJECT ID] Specify project id for API
|
25
|
+
--help Print this help
|
25
26
|
```
|
26
27
|
|
27
|
-
|
28
|
+
## Sample Config
|
28
29
|
```yaml
|
29
30
|
project_id: 1
|
30
31
|
branches:
|
@@ -34,5 +35,13 @@ api_url: http://example.com/dep_reporter/project/<project_id>/branch/<branch>
|
|
34
35
|
api_token: 1234567890
|
35
36
|
```
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
## Running as a global Git hook
|
39
|
+
See https://docs.gitlab.com/ee/administration/server_hooks.html#create-global-server-hooks-for-all-repositories
|
40
|
+
|
41
|
+
## Development
|
42
|
+
You can run Gurney locally from another directory like this:
|
43
|
+
|
44
|
+
```bash
|
45
|
+
cd some/real/project
|
46
|
+
ruby -I path/to/gurney/lib path/to/gurney/exe/gurney
|
47
|
+
```
|
data/lib/gurney/api.rb
CHANGED
@@ -9,13 +9,14 @@ module Gurney
|
|
9
9
|
@token = token
|
10
10
|
end
|
11
11
|
|
12
|
-
def post_dependencies(dependencies:, branch:, project_id:)
|
13
|
-
data = {
|
14
|
-
|
15
|
-
|
12
|
+
def post_dependencies(dependencies:, branch:, project_id:, repo_path: nil)
|
13
|
+
data = { dependencies: dependencies }
|
14
|
+
data[:repository_path] = repo_path if repo_path
|
15
|
+
|
16
16
|
url = base_url
|
17
17
|
url.gsub! '<project_id>', CGI.escape(project_id)
|
18
18
|
url.gsub! '<branch>', CGI.escape(branch)
|
19
|
+
|
19
20
|
post_json(url, data.to_json)
|
20
21
|
end
|
21
22
|
|
@@ -31,7 +32,7 @@ module Gurney
|
|
31
32
|
)
|
32
33
|
unless response.success?
|
33
34
|
if response.code == 404
|
34
|
-
raise ApiError.new("#{response.code}
|
35
|
+
raise ApiError.new("#{response.code} API url is probably wrong")
|
35
36
|
else
|
36
37
|
raise ApiError.new("#{response.code} #{response.body}")
|
37
38
|
end
|
data/lib/gurney/cli.rb
CHANGED
@@ -6,103 +6,116 @@ require 'git'
|
|
6
6
|
require 'fileutils'
|
7
7
|
|
8
8
|
module Gurney
|
9
|
+
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
9
12
|
class CLI
|
10
13
|
HOOK_STDIN_REGEX = /(?<old>[0-9a-f]{40}) (?<new>[0-9a-f]{40}) refs\/heads\/(?<ref>\w+)/m
|
11
14
|
CLIENT_HOOK_STDIN_REGEX = /refs\/heads\/(?<ref>\w+) (?<new>[0-9a-f]{40}) refs\/heads\/(?<remote_ref>\w+) (?<remote_sha>[0-9a-f]{40})/m
|
12
15
|
MAIN_BRANCHES = ['master', 'main'].freeze
|
13
16
|
|
14
17
|
def self.run(cmd_parameter=[])
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
file = read_file(g, options.hook, branch, options.config_file)
|
28
|
-
break file if file
|
29
|
-
end
|
30
|
-
if !config_file && options.hook
|
31
|
-
# dont run as a hook with no config
|
32
|
-
exit 0
|
33
|
-
end
|
34
|
-
config_file ||= '---'
|
35
|
-
config = Gurney::Config.from_yaml(config_file)
|
36
|
-
|
37
|
-
options.branches ||= config&.branches
|
38
|
-
options.branches ||= config&.branches
|
39
|
-
options.api_token ||= config&.api_token
|
40
|
-
options.api_url ||= config&.api_url
|
41
|
-
options.project_id ||= config&.project_id
|
42
|
-
|
43
|
-
if [options.project_id, options.branches, options.api_url, options.api_token].any?(&:nil?)
|
44
|
-
raise Gurney::Error.new("Either provide in a config file or set the flags for project id, branches, api url and api token")
|
45
|
-
end
|
46
|
-
|
47
|
-
branches = []
|
48
|
-
if options.hook || options.client_hook
|
49
|
-
# we get passed changed branches and refs via stdin
|
50
|
-
$stdin.each_line do |line|
|
51
|
-
regex = options.client_hook ? CLIENT_HOOK_STDIN_REGEX : HOOK_STDIN_REGEX
|
52
|
-
line.force_encoding(Encoding::UTF_8)
|
53
|
-
matches = line.match(regex)
|
54
|
-
if matches && matches[:new] != '0' * 40
|
55
|
-
if options.branches.include? matches[:ref]
|
56
|
-
branches << matches[:ref]
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
18
|
+
new(cmd_parameter).run
|
19
|
+
rescue SystemExit
|
20
|
+
# Do nothing
|
21
|
+
rescue Gurney::ApiError => e
|
22
|
+
puts "Gurney API error".red
|
23
|
+
puts e.message.red
|
24
|
+
rescue Gurney::Error => e
|
25
|
+
puts "Gurney error: #{e.message}".red
|
26
|
+
rescue Exception
|
27
|
+
puts "Gurney: an unexpected error occurred".red
|
28
|
+
raise
|
29
|
+
end
|
60
30
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
31
|
+
def initialize(cmd_parameter=[])
|
32
|
+
@options = Gurney::CLI::OptionParser.parse(cmd_parameter)
|
33
|
+
@git = if options.hook
|
34
|
+
Git.bare(ENV['GIT_DIR'] || Dir.pwd)
|
35
|
+
else
|
36
|
+
unless Dir.exist? './.git'
|
37
|
+
raise Gurney::Error.new('Must be run within a git repository')
|
67
38
|
end
|
39
|
+
Git.open('.')
|
40
|
+
end
|
68
41
|
|
69
|
-
|
70
|
-
|
42
|
+
config_file = MAIN_BRANCHES.find do |branch|
|
43
|
+
file = read_file(options.hook, branch, options.config_file)
|
44
|
+
break file if file
|
45
|
+
end
|
46
|
+
if options.hook && !config_file
|
47
|
+
# Git hooks are activated by the config file. Without, do nothing.
|
48
|
+
exit 0
|
49
|
+
end
|
50
|
+
config_file ||= '---'
|
51
|
+
config = Gurney::Config.from_yaml(config_file)
|
52
|
+
|
53
|
+
options.branches ||= config&.branches
|
54
|
+
options.branches ||= config&.branches
|
55
|
+
options.api_token ||= config&.api_token
|
56
|
+
options.api_url ||= config&.api_url
|
57
|
+
options.project_id ||= config&.project_id
|
58
|
+
|
59
|
+
missing_options = [:project_id, :branches, :api_url, :api_token].select { |option| options.send(option).nil? }
|
60
|
+
# Use the line below in development
|
61
|
+
# missing_options = [:project_id, :branches, :api_token].select { |option| options.send(option).nil? }
|
62
|
+
raise Gurney::Error.new("Incomplete config - missing #{missing_options.map(&:inspect).join(', ')}.") unless missing_options.empty?
|
63
|
+
end
|
71
64
|
|
72
|
-
|
73
|
-
|
65
|
+
def run
|
66
|
+
reporting_branches.each do |branch|
|
67
|
+
dependencies = []
|
74
68
|
|
75
|
-
|
76
|
-
|
69
|
+
yarn_source = Gurney::Source::Yarn.new(yarn_lock: read_file(options.hook || options.client_hook, branch, 'yarn.lock'))
|
70
|
+
dependencies.concat yarn_source.dependencies || []
|
77
71
|
|
78
|
-
|
79
|
-
|
72
|
+
bundler_source = Gurney::Source::Bundler.new(gemfile_lock: read_file(options.hook || options.client_hook, branch, 'Gemfile.lock'))
|
73
|
+
dependencies.concat bundler_source.dependencies || []
|
80
74
|
|
81
|
-
|
75
|
+
ruby_version_source = Gurney::Source::RubyVersion.new(ruby_version: read_file(options.hook || options.client_hook, branch, '.ruby-version'))
|
76
|
+
dependencies.concat ruby_version_source.dependencies || []
|
82
77
|
|
83
|
-
|
84
|
-
api.post_dependencies(dependencies: dependencies, branch: branch, project_id: options.project_id)
|
78
|
+
dependencies.compact!
|
85
79
|
|
86
|
-
|
87
|
-
|
88
|
-
end
|
80
|
+
api = Gurney::Api.new(base_url: options.api_url, token: options.api_token)
|
81
|
+
api.post_dependencies(dependencies: dependencies, branch: branch, project_id: options.project_id, repo_path: git.repo.path)
|
89
82
|
|
90
|
-
|
91
|
-
|
92
|
-
puts "Gurney: api error".red
|
93
|
-
puts e.message.red
|
94
|
-
rescue Gurney::Error => e
|
95
|
-
puts "Gurney: error".red
|
96
|
-
puts e.message.red
|
97
|
-
rescue Exception => e
|
98
|
-
puts "Gurney: an unexpected error occurred".red
|
99
|
-
raise
|
83
|
+
dependency_counts = dependencies.group_by(&:ecosystem).map{|ecosystem, dependencies| "#{ecosystem}: #{dependencies.count}" }.join(', ')
|
84
|
+
puts "Gurney: reported dependencies (#{dependency_counts})"
|
100
85
|
end
|
101
86
|
end
|
102
87
|
|
103
88
|
private
|
104
89
|
|
105
|
-
|
90
|
+
attr_accessor :git, :options
|
91
|
+
|
92
|
+
def reporting_branches
|
93
|
+
branches = []
|
94
|
+
if options.hook || options.client_hook
|
95
|
+
# We get changed branches and refs via stdin
|
96
|
+
# See https://git-scm.com/docs/githooks#post-receive
|
97
|
+
$stdin.each_line do |line|
|
98
|
+
regex = options.client_hook ? CLIENT_HOOK_STDIN_REGEX : HOOK_STDIN_REGEX
|
99
|
+
line.force_encoding(Encoding::UTF_8)
|
100
|
+
matches = line.match(regex)
|
101
|
+
if matches && matches[:new] != '0' * 40
|
102
|
+
if options.branches.include? matches[:ref]
|
103
|
+
branches << matches[:ref]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
else
|
108
|
+
current_branch = git.current_branch
|
109
|
+
unless options.branches.nil? || options.branches.include?(current_branch)
|
110
|
+
raise Gurney::Error.new('The current branch is not specified in the config.')
|
111
|
+
end
|
112
|
+
branches << current_branch
|
113
|
+
end
|
114
|
+
|
115
|
+
branches
|
116
|
+
end
|
117
|
+
|
118
|
+
def read_file(from_git, branch, filename)
|
106
119
|
if from_git
|
107
120
|
begin
|
108
121
|
git.show("#{branch}:#{filename}")
|
@@ -110,14 +123,9 @@ module Gurney
|
|
110
123
|
# happens if branch does not exist
|
111
124
|
end
|
112
125
|
else
|
113
|
-
if File.exist?
|
114
|
-
return File.read filename
|
115
|
-
end
|
126
|
+
File.read(filename) if File.exist?(filename)
|
116
127
|
end
|
117
128
|
end
|
118
129
|
|
119
130
|
end
|
120
|
-
|
121
|
-
class Error < Exception
|
122
|
-
end
|
123
131
|
end
|
data/lib/gurney/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gurney_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Schaflitzl
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|