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 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