gemnasium 3.2.1

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.
@@ -0,0 +1,52 @@
1
+ Then /^it should create the config directory$/ do
2
+ steps %{
3
+ Then the output should match:
4
+ """
5
+ Creating config directory
6
+ """
7
+ And a directory named "config" should exist
8
+ }
9
+ end
10
+ Then /^it should create the config file$/ do
11
+ steps %{
12
+ Then the output should match:
13
+ """
14
+ File created in .*\/config\/gemnasium\.yml
15
+ Please fill configuration file with accurate values\.
16
+ """
17
+ And a file named "config/gemnasium.yml" should exist
18
+ }
19
+ end
20
+ Then /^it should create the post-commit hook$/ do
21
+ steps %{
22
+ Then the output should match:
23
+ """
24
+ File created in .*\/.git\/hooks\/post-commit
25
+ """
26
+ And a file named ".git/hooks/post-commit" should exist
27
+ }
28
+ end
29
+ Then /^it should create the task file$/ do
30
+ steps %{
31
+ Then the output should match:
32
+ """
33
+ File created in .*\/lib\/tasks\/gemnasium.rake
34
+ """
35
+ And the output should contain:
36
+ """
37
+ Usage:
38
+ rake gemnasium:push - to push your dependency files
39
+ rake gemnasium:create - to create your project on Gemnasium
40
+ """
41
+ And a file named "lib/tasks/gemnasium.rake" should exist
42
+ }
43
+ end
44
+ Then /^it should create the tasks directory$/ do
45
+ steps %{
46
+ Then the output should match:
47
+ """
48
+ Creating lib/tasks directory.
49
+ """
50
+ And a directory named "lib/tasks" should exist
51
+ }
52
+ end
@@ -0,0 +1,7 @@
1
+ require 'aruba/cucumber'
2
+ require 'fileutils'
3
+
4
+ After do
5
+ test_project_path = "#{File.dirname(__FILE__)}../../../tmp/aruba/project"
6
+ FileUtils.rm_r File.expand_path(test_project_path) if File.exists? test_project_path
7
+ end
@@ -0,0 +1,24 @@
1
+ require './lib/gemnasium/version'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Tech-Angels"]
5
+ gem.email = ["contact@tech-angels.com"]
6
+ gem.description = "Safely upload your dependency files on gemnasium.com to track dependencies and get notified about updates and security advisories." +
7
+ "WARNING! This gem has been deprecated and support will be discontinued. Please use Gemnasium Toolbelt (https://github.com/gemnasium/toolbelt) instead."
8
+ gem.summary = gem.description
9
+ gem.homepage = "https://gemnasium.com/"
10
+ gem.license = 'MIT'
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "gemnasium"
15
+ gem.require_paths = ["lib"]
16
+ gem.executables = ["gemnasium"]
17
+ gem.version = Gemnasium::VERSION
18
+
19
+ gem.add_development_dependency 'rake', '~>10.3.1'
20
+ gem.add_development_dependency 'rspec', '~>3.0'
21
+ gem.add_development_dependency 'cucumber'
22
+ gem.add_development_dependency 'aruba'
23
+ gem.add_development_dependency 'webmock'
24
+ end
@@ -0,0 +1,328 @@
1
+ require 'json'
2
+ require 'gemnasium/connection'
3
+ require 'gemnasium/configuration'
4
+ require 'gemnasium/dependency_files'
5
+ require 'gemnasium/errors'
6
+
7
+ module Gemnasium
8
+ class << self
9
+
10
+ # Push dependency files to Gemnasium
11
+ # @param options [Hash] Parsed options from the command line. Options supported:
12
+ # * :project_path - Path to the project (required)
13
+ def push options
14
+ @config = load_config(options[:project_path])
15
+ ensure_config_is_up_to_date!
16
+
17
+ unless current_branch == @config.project_branch
18
+ if options[:ignore_branch]
19
+ notify "Not on tracked branch (#{@config.project_branch}), but ignore-branch option set to true. Continuing.\n", :blue
20
+ elsif options[:silence_branch]
21
+ notify "Not on tracked branch (#{@config.project_branch}). Exiting silently.\n", :blue
22
+ return
23
+ else
24
+ quit_because_of("Not on tracked branch (#{@config.project_branch})")
25
+ end
26
+ end
27
+
28
+ unless has_project_slug?
29
+ quit_because_of('Project slug not defined. Please create a new project or "resolve" the name of an existing project.')
30
+ end
31
+
32
+ dependency_files_hashes = DependencyFiles.get_sha1s_hash(options[:project_path])
33
+ quit_because_of("No supported dependency files detected.") if dependency_files_hashes.empty?
34
+ notify "#{dependency_files_hashes.keys.count} supported dependency file(s) found: #{dependency_files_hashes.keys.join(', ')}"
35
+
36
+ # Ask to Gemnasium server which dependency file should be uploaded (new or modified)
37
+ comparison_results = request("#{connection.api_path_for('dependency_files')}/compare", dependency_files_hashes)
38
+ files_to_upload = comparison_results['to_upload']
39
+ deleted_files = comparison_results['deleted']
40
+ notify "#{deleted_files.count} deleted file(s): #{deleted_files.join(', ')}", :blue unless deleted_files.empty?
41
+
42
+ unless files_to_upload.empty?
43
+ notify "#{files_to_upload.count} file(s) to upload: #{files_to_upload.join(', ')}"
44
+
45
+ # Upload requested dependency files content
46
+ upload_summary = request("#{connection.api_path_for('dependency_files')}/upload", DependencyFiles.get_content_to_upload(options[:project_path], files_to_upload))
47
+ notify "Added dependency files: #{upload_summary['added']}", :green
48
+ notify "Updated dependency files: #{upload_summary['updated']}", :green
49
+ notify "Unchanged dependency files: #{upload_summary['unchanged']}", :blue unless upload_summary['unchanged'].empty?
50
+ notify "Unsupported dependency files: #{upload_summary['unsupported']}", :blue unless upload_summary['unsupported'].empty?
51
+ else
52
+ notify "The project's dependency files are up-to-date.", :blue
53
+ end
54
+ rescue => exception
55
+ quit_because_of(exception.message)
56
+ end
57
+
58
+ # Install needed file(s) to run gemnasium command
59
+ #
60
+ # @param options [Hash] Parsed options from the command line. Options supported:
61
+ # * :install_rake_task - Install a rake task to the project
62
+ # * :install_git_hook - Install a git post-commit hook
63
+ # * :project_path - Path to the project (required)
64
+ def install options
65
+ require 'fileutils'
66
+
67
+ # Install config file
68
+ config_file_dir = "#{options[:project_path]}/config"
69
+
70
+ unless File.exists? config_file_dir
71
+ notify "Creating config directory"
72
+ FileUtils.mkdir_p config_file_dir
73
+ end
74
+
75
+ # Try to add config/gemnasium.yml to .gitignore
76
+ if File.exists? "#{options[:project_path]}/.gitignore"
77
+ File.open("#{options[:project_path]}/.gitignore", 'a+') do |f|
78
+ f.write("\n# Gemnasium gem configuration file\nconfig/gemnasium.yml") unless f.read.include? 'config/gemnasium.yml'
79
+ notify "Configuration file added to your project's .gitignore."
80
+ end
81
+ end
82
+
83
+ notify 'Please fill configuration file with accurate values.', :blue if copy_template('gemnasium.yml', "#{config_file_dir}/gemnasium.yml")
84
+
85
+ # Install git hook
86
+ if options[:install_git_hook]
87
+ notify ''
88
+ if File.exists? "#{options[:project_path]}/.git/hooks"
89
+ copy_template('post-commit', "#{options[:project_path]}/.git/hooks/post-commit")
90
+ else
91
+ notify "#{options[:project_path]} is not a git repository. Try to run `git init`.", :red
92
+ end
93
+ end
94
+
95
+ # Install rake task
96
+ if options[:install_rake_task]
97
+ notify ''
98
+ if !File.exists? "#{options[:project_path]}/Rakefile"
99
+ notify "Rakefile not found.", :red
100
+ else
101
+ rake_file_dir = "#{options[:project_path]}/lib/tasks"
102
+
103
+ unless File.exists? rake_file_dir
104
+ notify "Creating lib/tasks directory"
105
+ FileUtils.mkdir_p rake_file_dir
106
+ end
107
+
108
+ if copy_template('gemnasium.rake', "#{rake_file_dir}/gemnasium.rake")
109
+ notify 'Usage:', :blue
110
+ notify "\trake gemnasium:push \t\t- to push your dependency files", :blue
111
+ notify "\trake gemnasium:create \t\t- to create your project on Gemnasium", :blue
112
+ notify "\trake gemnasium:migrate \t\t- to migrate your configuration file", :blue
113
+ notify "\trake gemnasium:resolve \t\t- to resolve the project name to an existing project", :blue
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # Create the project on Gemnasium
120
+ #
121
+ # @param options [Hash] Parsed options from the command line. Options supported:
122
+ # * :project_path - Path to the project (required)
123
+ def create_project options
124
+ @config = load_config(options[:project_path])
125
+ ensure_config_is_up_to_date!
126
+
127
+ if has_project_slug?
128
+ quit_because_of("You already have a project slug refering to an existing project. Please remove this project slug from your configuration file to create a new project.")
129
+ end
130
+
131
+ project_params = { name: @config.project_name, branch: @config.project_branch}
132
+
133
+ result = request("#{connection.api_path_for('projects')}", project_params)
134
+ project_name, project_slug, remaining_slot_count = result.values_at 'name', 'slug', 'remaining_slot_count'
135
+
136
+ notify "Project `#{ project_name }` successfully created.", :green
137
+ notify "Project slug is `#{ project_slug }`.", :green
138
+ notify "Remaining private slots: #{ remaining_slot_count }", :blue
139
+
140
+ if @config.writable?
141
+ @config.store_value!(:project_slug, project_slug, "This unique project slug has been set by `gemnasium create`.")
142
+ notify "Your configuration file has been updated."
143
+ else
144
+ notify "Configuration file cannot be updated. Please edit the file and update the project slug manually."
145
+ end
146
+
147
+ rescue => exception
148
+ quit_because_of(exception.message)
149
+ end
150
+
151
+ # Migrate the configuration file
152
+ #
153
+ # @param options [Hash] Parsed options from the command line. Options supported:
154
+ # * :project_path - Path to the project (required)
155
+ #
156
+ def migrate options
157
+ @config = load_config(options[:project_path])
158
+
159
+ if @config.needs_to_migrate?
160
+ @config.migrate!
161
+ notify "Your configuration has been updated.", :green
162
+ notify "Run `gemnasium resolve` if your config is related to an existing project."
163
+ notify "Run `gemnasium create` if you want to create a new project on Gemnasium."
164
+ else
165
+ notify "Your configuration file is already up-to-date.", :green
166
+ end
167
+ end
168
+
169
+ # Find and store the project slug matching the given project name.
170
+ #
171
+ # @param options [Hash] Parsed options from the command line. Options supported:
172
+ # * :project_path - Path to the project (required)
173
+ #
174
+ def resolve_project options
175
+ @config = load_config(options[:project_path])
176
+ ensure_config_is_up_to_date!
177
+
178
+ # REFACTOR: similar code in #create_project
179
+ if has_project_slug?
180
+ quit_because_of("You already have a project slug refering to an existing project. Please remove this project slug from your configuration file.")
181
+ end
182
+
183
+ project_name = @config.project_name
184
+ project_branch = @config.project_branch || 'master'
185
+ projects = request("#{connection.api_path_for('projects')}")
186
+
187
+ candidates = projects.select do |project|
188
+ project['name'] == project_name && project['origin'] == 'offline' && project['branch'] == project_branch
189
+ end
190
+
191
+ criterias = "name `#{ project_name }` and branch `#{ project_branch }`"
192
+ if candidates.size == 0
193
+ quit_because_of("You have no off-line project matching #{ criterias }.")
194
+ elsif candidates.size > 1
195
+ quit_because_of("You have more than one off-line project matching #{ criterias }.")
196
+ end
197
+
198
+ project = candidates.first
199
+ project_slug = project['slug']
200
+ notify "Project slug is `#{ project_slug }`.", :green
201
+
202
+ # REFACTOR: similar code in #create_project
203
+ if @config.writable?
204
+ @config.store_value!(:project_slug, project_slug, "This unique project slug has been set by `gemnasium resolve`.")
205
+ notify "Your configuration file has been updated."
206
+ else
207
+ notify "Configuration file cannot be updated. Please edit the file and update the project slug manually."
208
+ end
209
+
210
+ rescue => exception
211
+ quit_because_of(exception.message)
212
+ end
213
+
214
+
215
+ def config
216
+ @config || quit_because_of('No configuration file loaded')
217
+ end
218
+
219
+ private
220
+
221
+ def has_project_slug?
222
+ !@config.project_slug.nil?
223
+ end
224
+
225
+ # Quit with an error message if the config file needs a migration.
226
+ #
227
+ def ensure_config_is_up_to_date!
228
+ if @config.needs_to_migrate?
229
+ quit_because_of('Your configuration file is not compatible with this version. Please run the `migrate` command first.')
230
+ end
231
+ end
232
+
233
+ # Issue a HTTP request
234
+ #
235
+ # @params path [String] Path of the request
236
+ # parameters [Hash] Parameters to send a POST request
237
+ def request(path, parameters = {})
238
+ if parameters.empty?
239
+ response = connection.get(path)
240
+ else
241
+ response = connection.post(path, JSON.generate(parameters))
242
+ end
243
+
244
+ raise Gemnasium::InvalidApiKeyError if response.code.to_i == 401
245
+
246
+ response_body = JSON.parse(response.body)
247
+
248
+ if response.code.to_i / 100 == 2
249
+ return {} if response_body.empty?
250
+ result = response_body
251
+ else
252
+ if error = "#{response_body['error']}_error".split('_').collect(&:capitalize).join
253
+ raise Gemnasium.const_get(error), response_body['message']
254
+ else
255
+ quit_because_of 'An unknown error has been returned by the server. Please contact Gemnasium support : http://support.gemnasium.com'
256
+ end
257
+ end
258
+ end
259
+
260
+ # Create a connection
261
+ def connection
262
+ @connection ||= Connection.new
263
+ end
264
+
265
+ # Load config from config file
266
+ #
267
+ # @param config_file [String] path to the project
268
+ # @return [Hash] config options
269
+ def load_config project_path
270
+ @config ||= Configuration.new("#{project_path}/config/gemnasium.yml")
271
+ rescue => exception
272
+ quit_because_of(exception.message)
273
+ end
274
+
275
+ # Puts a message to the standard output
276
+ #
277
+ # @param message [String] message to display
278
+ def notify message, color = nil
279
+ if $stdout.tty? && !color.nil?
280
+ color_code = { red: "\e[31m", green: "\e[32m", blue: "\e[34m" }
281
+ reset_color = "\e[0m"
282
+
283
+ message = color_code[color] + message + reset_color
284
+ end
285
+
286
+ $stdout.puts message
287
+ end
288
+
289
+ # Abort the program and colorize the message if $stderr is tty
290
+ #
291
+ # @param error_message [String] message to be puts to $stderr
292
+ def quit_because_of(error_message)
293
+ error_message = "\e[31m#{error_message}\e[0m" if $stderr.tty?
294
+ abort error_message
295
+ end
296
+
297
+ # Get the current git branch
298
+ #
299
+ # @return [String] name of the current branch
300
+ def current_branch
301
+ branch = `git branch 2>/dev/null`.split("\n").delete_if { |branch| branch.chars.first != "*" }.first
302
+ branch.gsub("* ","") unless branch.nil?
303
+ end
304
+
305
+ # Copy a template file
306
+ #
307
+ # @param file [String] template to copy
308
+ # target_path [String] location where to copy the template
309
+ def copy_template(file, target_path)
310
+ if File.exists? target_path
311
+ notify "The file #{target_path} already exists"
312
+ else
313
+ template_file = File.expand_path("#{File.dirname(__FILE__)}/templates/#{file}")
314
+ FileUtils.cp template_file, target_path
315
+
316
+ if File.exists? target_path
317
+ notify "File created in #{target_path}", :green
318
+
319
+ return true
320
+ else
321
+ notify "Could not install #{file} file.", :red
322
+ end
323
+ end
324
+
325
+ false
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,110 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ module Gemnasium
5
+ class Configuration
6
+ attr_accessor :site, :api_key, :use_ssl, :api_version, :project_branch, :ignored_paths
7
+
8
+ attr_accessor :project_name # Name is required only to create the project, make it mandatory.
9
+ attr_accessor :project_slug # Slug is required only to push the dependency files, make it optional.
10
+ attr_accessor :profile_name # Keep profile name for backward compatibility with version =< 2.0
11
+
12
+ DEFAULT_CONFIG = { 'site' => 'gemnasium.com',
13
+ 'use_ssl' => true,
14
+ 'api_version' => 'v3',
15
+ 'ignored_paths' => [] }
16
+
17
+ # Initialize the configuration object from a YAML file
18
+ #
19
+ # @param config_file [String] path to the configuration file
20
+ def initialize config_file
21
+ unless File.file?(config_file)
22
+ raise Errno::ENOENT,
23
+ "Configuration file (#{config_file}) does not exist.\nPlease run `gemnasium install`."
24
+ end
25
+ @path = config_file
26
+
27
+ config_hash = DEFAULT_CONFIG.merge(YAML.load(ERB.new(File.read(config_file)).result))
28
+ config_hash.each do |k, v|
29
+ writer_method = "#{k}="
30
+ if respond_to?(writer_method)
31
+ v = convert_ignored_paths_to_regexp(v) if k.to_s == 'ignored_paths'
32
+ send(writer_method, v)
33
+ end
34
+ end
35
+
36
+ raise 'Your configuration file does not contain all mandatory parameters or contain invalid values. Please check the documentation.' unless is_valid?
37
+ end
38
+
39
+ # Store a key-value pair in the configuration file with an optional comment.
40
+ # Try to preserve the comments and the indentation of the file.
41
+ # We assume the configuration file already features the given key.
42
+ #
43
+ # @param key [String] key
44
+ # @param value [String] value to store for given key
45
+ # @param comment [String] optional comment
46
+ #
47
+ def store_value!(key, value, comment = nil)
48
+ pattern = /\A#{ key }:.*\Z/
49
+ new_line = "#{ key }: #{ value }"
50
+ new_line += " # #{ comment }" if comment
51
+
52
+ content = File.readlines(path).map do |line|
53
+ line.rstrip.sub pattern, new_line
54
+ end.join("\n") + "\n"
55
+
56
+ File.write path, content
57
+ end
58
+
59
+ def writable?
60
+ File.writable? path
61
+ end
62
+
63
+ def needs_to_migrate?
64
+ !profile_name.nil?
65
+ end
66
+
67
+ def migrate!
68
+ # replace profile_name key with project_slug key (no value)
69
+ content = File.readlines(path).map do |line|
70
+ if line =~ /\Aprofile_name:.*\Z/
71
+ "project_slug:"
72
+ else
73
+ line
74
+ end
75
+ end.map{ |l| l.rstrip }.join("\n") + "\n"
76
+
77
+ File.write path, content
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :path
83
+
84
+ # Check that mandatory parameters are not nil and contain valid values
85
+ #
86
+ # @return [Boolean] if configuration is valid
87
+ def is_valid?
88
+ site_option_valid = !site.nil? && !site.empty?
89
+ api_key_option_valid = !api_key.nil? && !api_key.empty?
90
+ use_ssl_option_valid = !use_ssl.nil? && !!use_ssl == use_ssl # Check this is a boolean
91
+ api_version_option_valid = !api_version.nil? && !api_version.empty?
92
+ project_name_option_valid = !project_name.nil? && !project_name.empty?
93
+ ignored_paths_option_valid = ignored_paths.kind_of?(Array)
94
+
95
+ site_option_valid && api_key_option_valid && use_ssl_option_valid && api_version_option_valid &&
96
+ project_name_option_valid && ignored_paths_option_valid
97
+ end
98
+
99
+ def convert_ignored_paths_to_regexp(paths)
100
+ return [] unless paths.kind_of? Array
101
+
102
+ paths.inject([]) do |regexp_array, path|
103
+ path = path.insert(0,'^') # All path start from app root
104
+ .gsub('*','[^/]+') # Replace `*` to whatever char except slash
105
+ .gsub('.','\.') # Escape dots
106
+ regexp_array << Regexp.new(path)
107
+ end
108
+ end
109
+ end
110
+ end