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