gemnasium 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ require 'net/https'
2
+
3
+ module Gemnasium
4
+ class Connection
5
+ def initialize
6
+ @connection = Net::HTTP.new(Gemnasium.config.site, Gemnasium.config.use_ssl ? 443 : 80)
7
+ @connection.use_ssl = Gemnasium.config.use_ssl
8
+ end
9
+
10
+ def post(path, body, headers = {})
11
+ request = Net::HTTP::Post.new(path, headers.merge('Accept' => 'application/json', 'Content-Type' => 'application/json'))
12
+ request.basic_auth('X', Gemnasium.config.api_key)
13
+ request.body = body
14
+ @connection.request(request)
15
+ end
16
+
17
+ def get(path, headers = {})
18
+ request = Net::HTTP::Get.new(path, headers.merge('Accept' => 'application/json', 'Content-Type' => 'application/json'))
19
+ request.basic_auth('X', Gemnasium.config.api_key)
20
+ @connection.request(request)
21
+ end
22
+
23
+ # Set the API path for a specific item
24
+ #
25
+ # @param item [String] item the route should point to
26
+ # @return [String] API path
27
+ def api_path_for item
28
+ base = "/api/#{Gemnasium.config.api_version}"
29
+
30
+ case item
31
+ when 'base'
32
+ base
33
+ when 'projects'
34
+ "#{base}/projects"
35
+ when 'dependency_files'
36
+ "#{base}/projects/#{Gemnasium.config.project_slug}/dependency_files"
37
+ else
38
+ raise "No API path found for #{item}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ require 'digest/sha1'
2
+
3
+ module Gemnasium
4
+ class DependencyFiles
5
+
6
+ SUPPORTED_DEPENDENCY_FILES = /(Gemfile|Gemfile\.lock|.*\.gemspec|package\.json|npm-shrinkwrap\.json|setup\.py|requirements\.txt|requires\.txt|composer\.json|composer\.lock)$/
7
+
8
+ # Get a Hash of sha1s for each file corresponding to the regex
9
+ #
10
+ # @param regexp [Regexp] the regular expression of requested files
11
+ # @return [Hash] the hash associating each file path with its SHA1 hash
12
+ def self.get_sha1s_hash(project_path)
13
+ Dir.chdir(project_path)
14
+ Dir.glob("**/**").grep(SUPPORTED_DEPENDENCY_FILES).inject({}) do |h, file_path|
15
+ h[file_path] = calculate_sha1("#{project_path}/#{file_path}") unless is_ignored?(file_path)
16
+ h
17
+ end
18
+ end
19
+
20
+ # Get the content to upload to Gemnasium.
21
+ #
22
+ # @param files_path [Array] an array containing the path of the files
23
+ # @return [Array] array of hashes containing file name, file sha and file content
24
+ def self.get_content_to_upload(project_path, files_path)
25
+ files_path.inject([]) do |arr, file_path|
26
+ arr << { filename: file_path, sha: calculate_sha1(file_path), content: File.open("#{project_path}/#{file_path}") {|io| io.read} }
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # Calculate hash of a file in the same way git does
33
+ #
34
+ # @param file_path [String] path of the file
35
+ # @return [String] SHA1 of the file
36
+ def self.calculate_sha1(file_path)
37
+ mem_buf = File.open(file_path) {|io| io.read}
38
+ size = mem_buf.size
39
+ header = "blob #{size}\0" # type[space]size[null byte]
40
+
41
+ Digest::SHA1.hexdigest(header + mem_buf)
42
+ end
43
+
44
+ # Test if the file is ignored by the configuration file
45
+ #
46
+ # @param file_path [String] path of the file to test
47
+ # @return [Boolean] true if the file is ignored
48
+ def self.is_ignored?(file_path)
49
+ Gemnasium.config.ignored_paths.each do |ignored_path|
50
+ return true if file_path =~ ignored_path
51
+ end
52
+ false
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,16 @@
1
+ module Gemnasium
2
+ class InvalidApiKeyError < StandardError
3
+ def message
4
+ 'Your API key is invalid. Please double check it on https://gemnasium.com/settings/api_access'
5
+ end
6
+ end
7
+ class DeprecatedApiVersionError < StandardError; end
8
+ class MalformedRequestError < StandardError; end
9
+ class MissingParamsError < StandardError; end
10
+ class NonBillableUserError < StandardError; end
11
+ class NoSlotsAvailableError < StandardError; end
12
+ class ProjectNotFoundError < StandardError; end
13
+ class ProjectNotOfflineError < StandardError; end
14
+ class ProjectParamMissingError < StandardError; end
15
+ class UnsupportedDependencyFilesError < StandardError; end
16
+ end
@@ -0,0 +1,107 @@
1
+ require 'optparse'
2
+
3
+ module Gemnasium
4
+ class Options
5
+
6
+ # Parse arguments from command line
7
+ #
8
+ # @param args [Array] arguments from command line
9
+ # @return [Hash, OptionParser] hash of the parsed options & Option parser to get --help message
10
+ def self.parse args
11
+ options = {}
12
+
13
+ global = OptionParser.new do |opts|
14
+ opts.banner = 'Usage: gemnasium [options]'
15
+
16
+ opts.on '-v', '--version', 'Show Gemnasium version' do
17
+ options[:show_version] = true
18
+ end
19
+
20
+ opts.on '-h', '--help', 'Display this message' do
21
+ options[:show_help] = true
22
+ end
23
+
24
+ opts.separator ''
25
+ opts.separator <<-HELP_MESSAGE
26
+ Available commands are:
27
+ create : Create or update project on Gemnasium
28
+ install : Install the necessary config file
29
+ push : Push your dependency files to Gemnasium
30
+ migrate : Migrate the configuration file
31
+ resolve : Resolve project name to an existing project on Gemnasium
32
+
33
+ See `gemnasium COMMAND --help` for more information on a specific command.
34
+
35
+ WARNING! The gemnasium Rubygem has been deprecated.
36
+ Please use Gemnasium Toolbelt (https://github.com/gemnasium/toolbelt) instead.
37
+ HELP_MESSAGE
38
+ end
39
+
40
+ subcommands = {
41
+ 'create' => OptionParser.new do |opts|
42
+ opts.banner = 'Usage: gemnasium create [options]'
43
+
44
+ opts.on '-h', '--help', 'Display this message' do
45
+ options[:show_help] = true
46
+ end
47
+ end,
48
+ 'install' => OptionParser.new do |opts|
49
+ opts.banner = 'Usage: gemnasium install [options]'
50
+
51
+ opts.on '--git', 'Create a post-commit hook to run gemnasium push command if a dependency file has been commited' do
52
+ options[:install_git_hook] = true
53
+ end
54
+
55
+ opts.on '--rake', 'Create rake task to run Gemnasium' do
56
+ options[:install_rake_task] = true
57
+ end
58
+
59
+ opts.on '-h', '--help', 'Display this message' do
60
+ options[:show_help] = true
61
+ end
62
+ end,
63
+ 'push' => OptionParser.new do |opts|
64
+ opts.banner = 'Usage: gemnasium push'
65
+
66
+ opts.on '-i', '--ignore-branch', 'Push to gemnasium regardless of branch' do
67
+ options[:ignore_branch] = true
68
+ end
69
+
70
+ opts.on '-s', '--silence-branch', 'Silently ignore untracked branches' do
71
+ options[:silence_branch] = true
72
+ end
73
+
74
+ opts.on '-h', '--help', 'Display this message' do
75
+ options[:show_help] = true
76
+ end
77
+ end,
78
+ 'migrate' => OptionParser.new do |opts|
79
+ opts.banner = 'Usage: gemnasium migrate [options]'
80
+
81
+ opts.on '-h', '--help', 'Display this message' do
82
+ options[:show_help] = true
83
+ end
84
+ end,
85
+ 'resolve' => OptionParser.new do |opts|
86
+ opts.banner = 'Usage: gemnasium resolve [options]'
87
+
88
+ opts.on '-h', '--help', 'Display this message' do
89
+ options[:show_help] = true
90
+ end
91
+ end
92
+ }
93
+
94
+ global.order! args
95
+ parser = global
96
+
97
+ unless (command = args.shift).nil?
98
+ raise OptionParser::ParseError unless subcommands.has_key?(command)
99
+ subcommands[command].order! args
100
+ options[:command] = command
101
+ parser = subcommands[command]
102
+ end
103
+
104
+ return options, parser
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,3 @@
1
+ module Gemnasium
2
+ VERSION = "3.2.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ namespace :gemnasium do
2
+ require 'gemnasium'
3
+
4
+ desc "Push dependency files to gemnasium"
5
+ task :push do
6
+ Gemnasium.push project_path: File.expand_path(".")
7
+ end
8
+
9
+ desc "Create project on gemnasium"
10
+ task :create do
11
+ Gemnasium.create_project project_path: File.expand_path(".")
12
+ end
13
+
14
+ desc "Migrate the configuration file"
15
+ task :migrate do
16
+ Gemnasium.migrate project_path: File.expand_path(".")
17
+ end
18
+
19
+ desc "Resolve project name on gemnasium"
20
+ task :resolve do
21
+ Gemnasium.resolve_project project_path: File.expand_path(".")
22
+ end
23
+
24
+ end
@@ -0,0 +1,10 @@
1
+ api_key: api_key_goes_here # You personal (secret) API key. Get it at https://staging-gemnasium.tech-angels.net/settings#api_key
2
+ project_name: project_name # A name to remember your project.
3
+ project_slug: # Unique slug for this project. Get it on the "settings" page of your project or run "gemnasium resolve".
4
+ project_branch: master # /!\ If you don't use git, remove this line
5
+ ignored_paths: # Paths you want to ignore when searching for dependency files (from app root)
6
+ - features/
7
+ - spec/
8
+ - test/
9
+ - tmp/
10
+ - vendor/
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ SUPPORTED_COMMITED_FILES=$((git diff --name-only HEAD~ HEAD 2>/dev/null || git diff-tree -r --no-commit-id --name-only --root HEAD) | grep '^\(Gemfile\|Gemfile\.lock\|.*\.gemspec\|package\.json\|npm-shrinkwrap\.json\|setup\.py\|requirements\.txt\|requires\.txt\|composer\.json\|composer\.lock\)$')
4
+
5
+ if [[ $SUPPORTED_COMMITED_FILES > 0 ]]; then
6
+ gemnasium push
7
+ fi
@@ -0,0 +1,195 @@
1
+ require 'fileutils'
2
+ require 'spec_helper'
3
+
4
+ describe Gemnasium::Configuration do
5
+ describe 'default config' do
6
+ it { expect(Gemnasium::Configuration::DEFAULT_CONFIG['site']).to eql 'gemnasium.com' }
7
+ it { expect(Gemnasium::Configuration::DEFAULT_CONFIG['use_ssl']).to eql true }
8
+ it { expect(Gemnasium::Configuration::DEFAULT_CONFIG['api_version']).to eql 'v3' }
9
+ it { expect(Gemnasium::Configuration::DEFAULT_CONFIG['ignored_paths']).to eql [] }
10
+ end
11
+
12
+ let(:config_file_path) { 'tmp/config.yml' }
13
+ let(:config) { described_class.new File.expand_path(config_file_path) }
14
+
15
+ let(:config_options) do
16
+ {
17
+ api_key: 'api_key',
18
+ project_name: 'gemnasium-gem',
19
+ project_branch: 'master',
20
+ ignored_paths: ['spec/','tmp/*.lock', '*.gemspec']
21
+ }
22
+ end
23
+
24
+ def write_config_file
25
+ config_hash = config_options.reduce({}) do |memo, (key,value)|
26
+ memo ||= {}
27
+ memo[key.to_s] = value
28
+ memo
29
+ end
30
+ File.open(config_file_path, 'w+') { |f| f.write(config_hash.to_yaml) }
31
+ end
32
+
33
+ after do
34
+ File.delete(config_file_path) if File.exist?(config_file_path)
35
+ end
36
+
37
+ describe 'initialize' do
38
+ context 'for an inexistant config file' do
39
+ it { expect { Gemnasium::Configuration.new File.expand_path(config_file_path) }.to raise_error Errno::ENOENT }
40
+ end
41
+
42
+ context 'for a config file that does exist' do
43
+ before { FileUtils.touch(config_file_path) }
44
+
45
+ context 'with missing mandatory values' do
46
+ let(:config_options) {{ project_name: 'gemnasium-gem' }}
47
+ before { write_config_file }
48
+
49
+ it { expect { Gemnasium::Configuration.new File.expand_path(config_file_path) }.to raise_error('Your configuration file does not contain all mandatory parameters or contain invalid values. Please check the documentation.') }
50
+ end
51
+
52
+ context 'with all mandatory values' do
53
+ let(:config_options) {{ api_key: 'api_key', project_name: 'gemnasium-gem', project_branch: 'master', ignored_paths: ['spec/','tmp/*.lock', '*.gemspec'] }}
54
+ before { write_config_file }
55
+
56
+ it { expect(config.api_key).to eql config_options[:api_key] }
57
+
58
+ # Keep profile name for backward compatibility with version =< 2.0
59
+ it { expect(config.profile_name).to eql config_options[:profile_name] }
60
+
61
+ it { expect(config.project_name).to eql config_options[:project_name] }
62
+ it { expect(config.project_slug).to eql config_options[:project_slug] }
63
+ it { expect(config.project_branch).to eql config_options[:project_branch] }
64
+ it { expect(config.site).to eql Gemnasium::Configuration::DEFAULT_CONFIG['site'] }
65
+ it { expect(config.use_ssl).to eql Gemnasium::Configuration::DEFAULT_CONFIG['use_ssl'] }
66
+ it { expect(config.api_version).to eql Gemnasium::Configuration::DEFAULT_CONFIG['api_version'] }
67
+ it { expect(config.ignored_paths).to include Regexp.new("^spec/") }
68
+ it { expect(config.ignored_paths).to include Regexp.new("^tmp/[^/]+\\.lock") }
69
+ it { expect(config.ignored_paths).to include Regexp.new("^[^\/]+\\.gemspec") }
70
+ end
71
+ end
72
+
73
+ context 'for a config file with parsable ERB code' do
74
+ before { write_config_file }
75
+ before { ENV['GEMNASIUM_API_KEY'] = 'api_key_from_env' }
76
+
77
+ let(:config_options) do
78
+ {
79
+ api_key: "<%= ENV['GEMNASIUM_API_KEY'] %>",
80
+ project_name: 'gemnasium-gem',
81
+ project_branch: 'master',
82
+ ignored_paths: ['spec/','tmp/*.lock', '*.gemspec']
83
+ }
84
+ end
85
+
86
+ it 'parses ERB code' do
87
+ expect(config.api_key).to eql 'api_key_from_env'
88
+ end
89
+
90
+ it 'keeps values unchanged if not ERB code' do
91
+ expect(config.project_name).to eql config_options[:project_name]
92
+ expect(config.project_branch).to eql config_options[:project_branch]
93
+ expect(config.ignored_paths).to include Regexp.new("^spec/")
94
+ expect(config.ignored_paths).to include Regexp.new("^tmp/[^/]+\\.lock")
95
+ expect(config.ignored_paths).to include Regexp.new("^[^\/]+\\.gemspec")
96
+ end
97
+ end
98
+ end
99
+
100
+ pending "writable?"
101
+
102
+ describe "#store_value!" do
103
+ context "with a new value for an existing key" do
104
+ let(:key) { :project_name }
105
+ let(:value) { 'new-name' }
106
+
107
+ # HACK: fake config reload
108
+ let(:new_config) { Gemnasium::Configuration.new File.expand_path(config_file_path) }
109
+
110
+ before do
111
+ write_config_file
112
+ config.store_value! key, value, "my project name"
113
+ end
114
+
115
+ it "updates the given key-value pair" do
116
+ expect(new_config.project_name).to eql 'new-name'
117
+ end
118
+
119
+ it "keeps other key-value pairs unchanged" do
120
+ expect(new_config.api_key).to eql config_options[:api_key]
121
+ expect(new_config.project_branch).to eql config_options[:project_branch]
122
+ end
123
+
124
+ it "stores the given comment" do
125
+ content = File.read File.expand_path(config_file_path)
126
+ expect(content).to match /project_name:.*# my project name/
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#migrate!" do
132
+ before { write_config_file }
133
+ subject { config.needs_to_migrate? }
134
+
135
+ context "with NO profile_name key" do
136
+ let(:config_options) do
137
+ {
138
+ api_key: 'api_key',
139
+ project_name: 'gemnasium-gem',
140
+ project_branch: 'master'
141
+ # no profile_name
142
+ }
143
+ end
144
+
145
+ it { should be false }
146
+ end
147
+
148
+ context "with profile_name key" do
149
+ let(:config_options) do
150
+ {
151
+ project_name: 'gemnasium-gem',
152
+ profile_name: 'tech-angels',
153
+ api_key: '1337'
154
+ }
155
+ end
156
+
157
+ it { should be true }
158
+ end
159
+ end
160
+
161
+ describe "#migrate!" do
162
+ before { write_config_file }
163
+
164
+ let(:config_options) do
165
+ {
166
+ project_name: 'gemnasium-gem',
167
+ profile_name: 'tech-angels',
168
+ api_key: '1337'
169
+ }
170
+ end
171
+
172
+ # HACK: fake config reload
173
+ let(:new_config) { Gemnasium::Configuration.new File.expand_path(config_file_path) }
174
+
175
+ it "removes the profile_name" do
176
+ expect(config.profile_name).to eql 'tech-angels'
177
+ config.migrate!
178
+ expect(new_config.profile_name).to eql nil
179
+ end
180
+
181
+ it "adds an empty project_slug" do
182
+ config.migrate!
183
+ expect(new_config.project_slug).to eql nil
184
+
185
+ content = File.read File.expand_path(config_file_path)
186
+ expect(content).to match /^project_slug:$/
187
+ end
188
+
189
+ it "preserves the other keys" do
190
+ config.migrate!
191
+ expect(new_config.project_name).to eql 'gemnasium-gem'
192
+ expect(new_config.api_key).to eql '1337'
193
+ end
194
+ end
195
+ end