gurney_client 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce89572fd28ab8810c0c4783e211e984ec8675d52e5e99f7ec3c0fca0af1404d
4
- data.tar.gz: '009d1d819bdf23e70fd0004bdf421b25f9c73f93dba47aca28f7cf6070e33e83'
3
+ metadata.gz: 6e7470d3ea652657be230f28b42b3035ee0e2c47122dc297533428831fc20b2b
4
+ data.tar.gz: 7e3bf5796d850cc8e600959a348f521d9d7ee10f5f7e329da391c0b125f57db0
5
5
  SHA512:
6
- metadata.gz: fcdd4b685a61d3f3179d1803343f21b8554694a9f2e3af52a1629d3d22c8323eb2b4cbd3f7e0d96d860690eb67b6c158e2268e8fabd5f171663a1d550de3ca41
7
- data.tar.gz: 2f73d012863be290165d7944fdaaecdc2d3c28de70b7ce08c2c97ca27feb933c1d514c92f9a4e1c04c44c2b7a18765658e3b3f6bc59fe1c9337ca73ecb7ba852
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.2.3)
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.4)
14
- public_suffix (>= 2.0.2, < 6.0)
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.0)
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
- mime-types (3.4.1)
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.2023.0218.1)
28
+ mime-types-data (3.2024.1105)
27
29
  multi_xml (0.6.0)
28
- public_suffix (5.0.1)
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.2.27
57
+ 2.3.19
data/README.md CHANGED
@@ -1,30 +1,31 @@
1
- ## Gurney
1
+ # Gurney
2
2
 
3
- Gurney is a small tool to extract dependencies from project files and report them to a web api.
4
- It can either run locally or as a git post-receive hook in gitlab.
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 git hook, the project gets cloned on the git server and gurney then looks for a `gurney.yml` within the project files.
7
- If its present gurney looks at the pushed branches and analyses the ones specified in the config for dependencies.
8
- It then reports them to the web api also specified in the config.
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
- #### Usage:
15
+ ## Usage
11
16
  ```
12
17
  Usage: gurney [options]
13
- --api-url [API URL]
14
- Url for web api call, can have parameters for <project_id> and <branch>
15
- example: --api-url "http://example.com/project/<project_id>/branch/<branch>"
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 git post-receive hook
20
- --client-hook
21
- Run as a git pre-push hook
22
- -p, --project-id [PROJECT ID] Specify project id for api
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
- #### Sample Config:
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
- ##### Running as a global git hook
38
- To run as a global git hook in your gitlab see https://docs.gitlab.com/ee/administration/custom_hooks.html#set-a-global-git-hook-for-all-repositories
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
- dependencies: dependencies
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} api url is probably wrong")
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
- options = Gurney::CLI::OptionParser.parse(cmd_parameter)
16
-
17
- begin
18
- if options.hook
19
- g = Git.bare(ENV['GIT_DIR'] || Dir.pwd)
20
- else
21
- unless Dir.exist? './.git'
22
- raise Gurney::Error.new('Must be run within a git repository')
23
- end
24
- g = Git.open('.')
25
- end
26
- config_file = MAIN_BRANCHES.find do |branch|
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
- else
62
- current_branch = g.current_branch
63
- unless options.branches.nil? || options.branches.include?(current_branch)
64
- raise Gurney::Error.new('The current branch is not specified in the config.')
65
- end
66
- branches << current_branch
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
- branches.each do |branch|
70
- dependencies = []
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
- yarn_source = Gurney::Source::Yarn.new(yarn_lock: read_file(g, options.hook || options.client_hook, branch, 'yarn.lock'))
73
- dependencies.concat yarn_source.dependencies || []
65
+ def run
66
+ reporting_branches.each do |branch|
67
+ dependencies = []
74
68
 
75
- bundler_source = Gurney::Source::Bundler.new(gemfile_lock: read_file(g, options.hook || options.client_hook, branch, 'Gemfile.lock'))
76
- dependencies.concat bundler_source.dependencies || []
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
- ruby_version_source = Gurney::Source::RubyVersion.new(ruby_version: read_file(g, options.hook || options.client_hook, branch, '.ruby-version'))
79
- dependencies.concat ruby_version_source.dependencies || []
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
- dependencies.compact!
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
- api = Gurney::Api.new(base_url: options.api_url, token: options.api_token)
84
- api.post_dependencies(dependencies: dependencies, branch: branch, project_id: options.project_id)
78
+ dependencies.compact!
85
79
 
86
- dependency_counts = dependencies.group_by(&:ecosystem).map{|ecosystem, dependencies| "#{ecosystem}: #{dependencies.count}" }.join(', ')
87
- puts "Gurney: reported dependencies (#{dependency_counts})"
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
- rescue SystemExit
91
- rescue Gurney::ApiError => e
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
- def self.read_file(git, from_git, branch, filename)
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? filename
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
@@ -1,3 +1,3 @@
1
1
  module Gurney
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
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.3.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-14 00:00:00.000000000 Z
11
+ date: 2024-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize