gurney_client 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 64bb60a1de5168633f0f1fd0605030edffe46440472dfdf64bbb56862f40f3d4
4
+ data.tar.gz: 3a95353020d09220c0dd989c59f8d4ff84579b2da7795a2fef630e5ca42d688e
5
+ SHA512:
6
+ metadata.gz: 1967532e08ba47400be443ce8bdbd65b1690fc6dc75d1f35d6e5121dcd02b5948ff97c870cd60395c5b83288c432bc25db2f1bb9732286cb6e3da629ca2139a0
7
+ data.tar.gz: b44ded67b9c21e227ba567722ca27546319590b6c5c895d2b1950455704ac45f34a843402b003f7618af348f298483cf861887697ec7cd8023f95ddbfeb0d349
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .idea/
2
+ *.gem
3
+ .byebug_history
4
+ gurney.yml
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6.1
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'colorize'
4
+ gem 'httparty'
5
+ gem 'rspec'
6
+ gem 'byebug'
7
+ gem 'bundler'
8
+ gem 'git'
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ byebug (11.0.1)
5
+ colorize (0.8.1)
6
+ diff-lcs (1.3)
7
+ git (1.5.0)
8
+ httparty (0.17.1)
9
+ mime-types (~> 3.0)
10
+ multi_xml (>= 0.5.2)
11
+ mime-types (3.3)
12
+ mime-types-data (~> 3.2015)
13
+ mime-types-data (3.2019.1009)
14
+ multi_xml (0.6.0)
15
+ rspec (3.9.0)
16
+ rspec-core (~> 3.9.0)
17
+ rspec-expectations (~> 3.9.0)
18
+ rspec-mocks (~> 3.9.0)
19
+ rspec-core (3.9.0)
20
+ rspec-support (~> 3.9.0)
21
+ rspec-expectations (3.9.0)
22
+ diff-lcs (>= 1.2.0, < 2.0)
23
+ rspec-support (~> 3.9.0)
24
+ rspec-mocks (3.9.0)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.9.0)
27
+ rspec-support (3.9.0)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ bundler
34
+ byebug
35
+ colorize
36
+ git
37
+ httparty
38
+ rspec
39
+
40
+ BUNDLED WITH
41
+ 1.17.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Martin Schaflitzl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ ## Gurney
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.
5
+
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.
9
+
10
+ #### Usage:
11
+ ```
12
+ 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
+ -c, --config [CONFIG FILE] Config file to use
19
+ -h, --hook Run as a git post-receive hook
20
+ -p, --project-id [PROJECT ID] Specify project id for api
21
+ --help
22
+ Prints this help
23
+ ```
24
+
25
+ #### Sample Config:
26
+ ```yaml
27
+ project_id: 1
28
+ branches:
29
+ - master
30
+ - production
31
+ api_url: http://example.com/dep_reporter/project/<project_id>/branch/<branch>
32
+ api_token: 1234567890
33
+ ```
34
+
35
+ ##### Running as a global git hook
36
+ 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
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/exe/gurney ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gurney'
4
+
5
+ Gurney::CLI.run(ARGV)
data/git_hook_sample ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ gurney --hook
data/gurney.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "gurney/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "gurney_client"
7
+ spec.version = Gurney::VERSION
8
+ spec.authors = ["Martin Schaflitzl"]
9
+ spec.email = ["martin.schaflitzl@makandra.de"]
10
+
11
+ spec.summary = 'Gurney is a small tool to extract yarn and RubyGems dependencies from project files and report them to a web api.'
12
+ spec.homepage = "https://github.com/makandra/gurney"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(test|spec|features|bin)/}) }
16
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'colorize', '~> 0.8'
22
+ spec.add_runtime_dependency 'httparty', '~> 0.17.1'
23
+ spec.add_runtime_dependency 'bundler', '< 3'
24
+ spec.add_runtime_dependency 'git', '~> 1.5'
25
+ end
data/gurney.yml.sample ADDED
@@ -0,0 +1,6 @@
1
+ project_id: 1
2
+ branches:
3
+ - master
4
+ - production
5
+ api_url: http://example.com/dep_reporter/project/<project_id>/branch/<branch>
6
+ api_token: 1234567890
data/lib/gurney/api.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'httparty'
2
+ require 'cgi'
3
+
4
+ module Gurney
5
+ class Api
6
+
7
+ def initialize(base_url:, token:)
8
+ @base_url = base_url
9
+ @token = token
10
+ end
11
+
12
+ def post_dependencies(dependencies:, branch:, project_id:)
13
+ data = {
14
+ dependencies: dependencies
15
+ }
16
+ url = base_url
17
+ url.gsub! '<project_id>', CGI.escape(project_id)
18
+ url.gsub! '<branch>', CGI.escape(branch)
19
+ post_json(url, data.to_json)
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :base_url, :token
25
+
26
+ def post_json(url, json)
27
+ response = HTTParty.post(url,
28
+ body: json,
29
+ headers: { 'X-AuthToken' => @token,
30
+ 'Content-Type': 'application/json'},
31
+ )
32
+ unless response.success?
33
+ if response.code == 404
34
+ raise ApiError.new("#{response.code} api url is probably wrong")
35
+ else
36
+ raise ApiError.new("#{response.code} #{response.body}")
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ class ApiError < Exception
44
+ end
45
+ end
@@ -0,0 +1,51 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ module Gurney
5
+ class CLI
6
+ class OptionParser
7
+
8
+ def self.parse(args)
9
+ options = OpenStruct.new
10
+ options.hook = false
11
+ options.config_file = 'gurney.yml'
12
+
13
+ option_parser = ::OptionParser.new do |opts|
14
+ opts.banner = "Usage: gurney [options]"
15
+
16
+ opts.on('',
17
+ '--api-url [API URL]',
18
+ "Url for web api call, can have parameters for <project_id> and <branch>" ,
19
+ "example: --api-url \"http://example.com/project/<project_id>/branch/<branch>\"") do |api_url|
20
+ options.api_url = api_url
21
+ end
22
+
23
+ opts.on('', '--api-token [API TOKEN]', 'Token to be send to api in the X-AuthToken header') do |api_token|
24
+ options.api_token = api_token
25
+ end
26
+
27
+ opts.on('-c', '--config [CONFIG FILE]', 'Config file to use') do |config_file|
28
+ options.config_file = config_file
29
+ end
30
+
31
+ opts.on('-h', '--hook', 'Run as a git post-receive hook') do |hook|
32
+ options.hook = hook
33
+ end
34
+
35
+ opts.on('-p', '--project-id [PROJECT ID]', 'Specify project id for api') do |project_id|
36
+ options.project_id = project_id
37
+ end
38
+
39
+ opts.on_tail('', '--help', 'Prints this help') do
40
+ puts opts
41
+ exit
42
+ end
43
+ end
44
+
45
+ option_parser.parse!(args)
46
+ options
47
+ end
48
+
49
+ end
50
+ end
51
+ end
data/lib/gurney/cli.rb ADDED
@@ -0,0 +1,112 @@
1
+ require 'optparse'
2
+ require 'securerandom'
3
+ require 'colorize'
4
+ require 'open3'
5
+ require 'git'
6
+ require 'fileutils'
7
+ require 'byebug'
8
+
9
+ module Gurney
10
+ class CLI
11
+
12
+ HOOK_STDIN_REGEX = /(?<old>[0-9a-f]{40}) (?<new>[0-9a-f]{40}) refs\/heads\/(?<ref>\w+)/m
13
+
14
+ 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'])
20
+ else
21
+ unless Dir.exists? './.git'
22
+ raise Gurney::Error.new('Must be run within a git repository')
23
+ end
24
+ g = Git.open('.')
25
+ end
26
+ config_file = read_file(g, options.hook, 'master', options.config_file)
27
+ if !config_file && options.hook
28
+ # dont run as a hook with no config
29
+ exit 0
30
+ end
31
+ config_file ||= '---'
32
+ config = Gurney::Config.from_yaml(config_file)
33
+
34
+ options.branches ||= config&.branches
35
+ options.api_token ||= config&.api_token
36
+ options.api_url ||= config&.api_url
37
+ options.project_id ||= config&.project_id
38
+
39
+ if [options.project_id, options.branches, options.api_url, options.api_token].any?(&:nil?)
40
+ raise Gurney::Error.new("Either provide in a config file or set the flags for project id, branches, api url and api token")
41
+ end
42
+
43
+ branches = []
44
+ if options.hook
45
+ # we get passed changed branches and refs via stdin
46
+ $stdin.each_line do |line|
47
+ matches = line.match(HOOK_STDIN_REGEX)
48
+ unless matches[:new] == '0' * 40
49
+ if options.branches.include? matches[:ref]
50
+ branches << matches[:ref]
51
+ end
52
+ end
53
+ end
54
+
55
+ else
56
+ current_branch = g.current_branch
57
+ unless options.branches.nil? || options.branches.include?(current_branch)
58
+ raise Gurney::Error.new('The current branch is not specified in the config.')
59
+ end
60
+ branches << current_branch
61
+ end
62
+
63
+ branches.each do |branch|
64
+ dependencies = []
65
+
66
+ yarn_source = Gurney::Source::Yarn.new(yarn_lock: read_file(g, options.hook, branch, 'yarn.lock'))
67
+ dependencies.concat yarn_source.dependencies || []
68
+
69
+ bundler_source = Gurney::Source::Bundler.new(gemfile_lock: read_file(g, options.hook, branch, 'Gemfile.lock'))
70
+ dependencies.concat bundler_source.dependencies || []
71
+
72
+ dependencies.compact!
73
+
74
+ api = Gurney::Api.new(base_url: options.api_url, token: options.api_token)
75
+ api.post_dependencies(dependencies: dependencies, branch: branch, project_id: options.project_id)
76
+
77
+ dependency_counts = dependencies.group_by(&:ecosystem).map{|ecosystem, dependencies| "#{ecosystem}: #{dependencies.count}" }.join(', ')
78
+ puts "Gurney: reported dependencies (#{dependency_counts})"
79
+ end
80
+
81
+ rescue SystemExit
82
+ rescue Gurney::ApiError => e
83
+ puts "Gurney: api error".red
84
+ puts e.message.red
85
+ rescue Gurney::Error => e
86
+ puts "Gurney: error".red
87
+ puts e.message.red
88
+ rescue Exception => e
89
+ puts "Gurney: an unexpected error occurred".red
90
+ puts "#{e.full_message}"
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def self.read_file(git, from_git, branch, filename)
97
+ if from_git
98
+ if git.ls_tree(branch)['blob'].key?(filename)
99
+ return git.show("#{branch}:#{filename}")
100
+ end
101
+ else
102
+ if File.exists? filename
103
+ return File.read filename
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ class Error < Exception
111
+ end
112
+ end
@@ -0,0 +1,21 @@
1
+ require 'yaml'
2
+
3
+ module Gurney
4
+ class Config
5
+
6
+ attr_accessor :branches, :api_url, :api_token, :project_id
7
+
8
+ def initialize(branches: nil, api_url: nil, api_token: nil, project_id: nil)
9
+ @branches = branches
10
+ @api_url = api_url
11
+ @api_token = api_token&.to_s
12
+ @project_id = project_id&.to_s
13
+ end
14
+
15
+ def self.from_yaml(yaml)
16
+ config = YAML.load(yaml)&.map{|(k,v)| [k.to_sym,v]}.to_h
17
+ new(**config)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ module Gurney
2
+ class Dependency
3
+
4
+ attr_reader :ecosystem, :name, :version
5
+
6
+ def initialize(ecosystem:, name:, version:)
7
+ @ecosystem = ecosystem
8
+ @name = name
9
+ @version = version
10
+ end
11
+
12
+ def to_json(*args)
13
+ {
14
+ ecosystem: @ecosystem,
15
+ name: @name,
16
+ version: @version,
17
+ }.to_json(*args)
18
+ end
19
+
20
+ def ==(other)
21
+ other.class == self.class &&
22
+ other.ecosystem == ecosystem &&
23
+ other.name == name &&
24
+ other.version == version
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ module Gurney
2
+ module Source
3
+ class Base
4
+
5
+ def present?
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def dependencies
10
+ raise NotImplementedError
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ require 'bundler'
2
+
3
+ module Gurney
4
+ module Source
5
+ class Bundler < Base
6
+
7
+ def initialize(gemfile_lock:)
8
+ @gemfile_lock = gemfile_lock
9
+ end
10
+
11
+ def present?
12
+ !@gemfile_lock.nil?
13
+ end
14
+
15
+ def dependencies
16
+ if present?
17
+ Dir.mktmpdir do |dir|
18
+ Dir.chdir dir do
19
+ File.write('Gemfile', '') # LockfileParser requires a Gemfile to be present, can be empty
20
+ lockfile = ::Bundler::LockfileParser.new(@gemfile_lock)
21
+ lockfile.specs.map { |spec| Dependency.new(ecosystem: 'rubygems', name: spec.name, version: spec.version.to_s) }
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :gemfile_lock
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ module Gurney
2
+ module Source
3
+ class Yarn < Base
4
+
5
+ YARN_LOCK_REGEX = /^"?(?<name>\S*)@.+?"?:\n\s{2}version "(?<version>.+?)"$/m
6
+
7
+ def initialize(yarn_lock:)
8
+ @yarn_lock = yarn_lock
9
+ end
10
+
11
+ def present?
12
+ !@yarn_lock.nil?
13
+ end
14
+
15
+ def dependencies
16
+ if present?
17
+ dependencies = @yarn_lock.scan(YARN_LOCK_REGEX).map{|match| { name: match[0], version: match[1] } }
18
+ dependencies.map { |dependency| Dependency.new(ecosystem: 'npm', **dependency) }
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :yarn_lock
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Gurney
2
+ VERSION = "0.1.1"
3
+ end
data/lib/gurney.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative 'gurney/version'
2
+ require_relative 'gurney/config'
3
+ require_relative 'gurney/dependency'
4
+ require_relative 'gurney/source/base'
5
+ require_relative 'gurney/source/yarn'
6
+ require_relative 'gurney/source/bundler'
7
+ require_relative 'gurney/api'
8
+ require_relative 'gurney/cli/option_parser'
9
+ require_relative 'gurney/cli'
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gurney_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Martin Schaflitzl
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.17.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.17.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "<"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "<"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: git
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.5'
69
+ description:
70
+ email:
71
+ - martin.schaflitzl@makandra.de
72
+ executables:
73
+ - gurney
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".ruby-version"
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - exe/gurney
86
+ - git_hook_sample
87
+ - gurney.gemspec
88
+ - gurney.yml.sample
89
+ - lib/gurney.rb
90
+ - lib/gurney/api.rb
91
+ - lib/gurney/cli.rb
92
+ - lib/gurney/cli/option_parser.rb
93
+ - lib/gurney/config.rb
94
+ - lib/gurney/dependency.rb
95
+ - lib/gurney/source/base.rb
96
+ - lib/gurney/source/bundler.rb
97
+ - lib/gurney/source/yarn.rb
98
+ - lib/gurney/version.rb
99
+ homepage: https://github.com/makandra/gurney
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.0.1
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Gurney is a small tool to extract yarn and RubyGems dependencies from project
122
+ files and report them to a web api.
123
+ test_files: []