gemnasium 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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