api_deploy 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +1 -0
- data/.gitattributes +1 -0
- data/.gitignore +37 -0
- data/.vault_pass.py +4 -0
- data/Dockerfile +29 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +73 -0
- data/Jenkinsfile +14 -0
- data/Makefile +38 -0
- data/README.md +38 -0
- data/Rakefile +9 -0
- data/api_deploy.gemspec +16 -0
- data/bin/api_deploy +6 -0
- data/bin/apply_restrictions +7 -0
- data/bin/bitbucket +7 -0
- data/bin/get_all_repo_sizes +18 -0
- data/bin/ldap +7 -0
- data/config/defaults.json +21 -0
- data/config/vault +0 -0
- data/lib/api.rb +45 -0
- data/lib/api_deploy.rb +5 -0
- data/lib/artifactory_api.rb +13 -0
- data/lib/bitbucket.rb +68 -0
- data/lib/bitbucket/project.rb +34 -0
- data/lib/bitbucket/repository.rb +66 -0
- data/lib/config_store.rb +40 -0
- data/lib/github.rb +13 -0
- data/lib/ldap.rb +115 -0
- data/lib/libraries.rb +10 -0
- data/lib/log.rb +13 -0
- data/lib/octopus_api.rb +56 -0
- data/lib/teamcity_api.rb +40 -0
- data/spec/data/defaults.json +16 -0
- data/spec/data/overrides.json +5 -0
- data/spec/lib/api_spec.rb +52 -0
- data/spec/lib/artifactory_api_spec.rb +10 -0
- data/spec/lib/config_spec.rb +30 -0
- data/spec/lib/log_spec.rb +15 -0
- data/spec/lib/octopus_api_spec.rb +95 -0
- data/spec/lib/teamcity_api_spec.rb +20 -0
- data/spec/spec_helper.rb +2 -0
- metadata +224 -0
data/lib/bitbucket.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
class Bitbucket
|
2
|
+
include API
|
3
|
+
LIMIT=1000.to_s
|
4
|
+
|
5
|
+
HOOK_EXCLUSIONS = ['WSC','CM','YOMS']
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
create_api( ConfigStore.bitbucket )
|
9
|
+
end
|
10
|
+
|
11
|
+
def projects
|
12
|
+
@projects ||= Project.get_all(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_master_branch_protected_on_all_projects
|
16
|
+
projects.map do |project|
|
17
|
+
project.repositories.map do |r|
|
18
|
+
r.set_master_branch_protected unless r.master_branch_protected?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_hook_status_on_all_projects(key,status,settings=nil)
|
24
|
+
projects.map do |project|
|
25
|
+
next if HOOK_EXCLUSIONS.include?(project.key)
|
26
|
+
project.set_hook_status(key, status, settings)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_hooks_on_exclusions
|
31
|
+
projects.select {|p| HOOK_EXCLUSIONS.include? p.key }.each do |project|
|
32
|
+
[
|
33
|
+
'org.christiangalsterer.stash-filehooks-plugin:filesize-hook',
|
34
|
+
"org.christiangalsterer.stash-filehooks-plugin:filename-hook",
|
35
|
+
].each do |key|
|
36
|
+
project.set_hook_status(key, false)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def apply_restrictions
|
42
|
+
set_hook_status_on_all_projects(
|
43
|
+
"org.christiangalsterer.stash-filehooks-plugin:filesize-hook",
|
44
|
+
true,
|
45
|
+
{
|
46
|
+
"pattern-1"=>".*",
|
47
|
+
"size-1"=>"10485760",
|
48
|
+
"pattern-exclude-1"=>"",
|
49
|
+
"pattern-branches-1"=>""
|
50
|
+
}.to_json
|
51
|
+
)
|
52
|
+
|
53
|
+
set_hook_status_on_all_projects(
|
54
|
+
"org.christiangalsterer.stash-filehooks-plugin:filename-hook",
|
55
|
+
false,
|
56
|
+
{
|
57
|
+
"pattern"=>"\\.(h26.?|mp.?.?|avi|webm|flv|tar(\\..?.?.?)?|zip|7z|rar|exe|msi|rpm|deb)$",
|
58
|
+
"pattern-exclude"=>"",
|
59
|
+
"pattern-branches"=>""
|
60
|
+
}.to_json
|
61
|
+
)
|
62
|
+
|
63
|
+
set_master_branch_protected_on_all_projects
|
64
|
+
|
65
|
+
# cleanup TRAIN project
|
66
|
+
projects.detect {|p| p.key == 'TRAIN'}.move_all_repos_to_project('TOLD')
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Project
|
2
|
+
include API
|
3
|
+
LIMIT=1000.to_s
|
4
|
+
|
5
|
+
def self.get_all(server)
|
6
|
+
data = server.request(:get, "rest/api/1.0/projects?limit=#{LIMIT}")
|
7
|
+
data['values'].map {|p| new(server,p) }
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :name, :key
|
11
|
+
|
12
|
+
def initialize(server,data)
|
13
|
+
@bb = server
|
14
|
+
@name = data['name']
|
15
|
+
@key = data['key']
|
16
|
+
end
|
17
|
+
|
18
|
+
def repositories
|
19
|
+
@repositories ||= Repository.get_all(@bb,key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def move_all_repos_to_project(new_project)
|
23
|
+
repositories.each do |r|
|
24
|
+
r.move_repo_to_project(new_project)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_hook_status(key,status,settings=nil)
|
29
|
+
repositories.each do |r|
|
30
|
+
r.set_hook_settings(key, settings) if status && settings
|
31
|
+
r.set_hook_status(key, status)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Repository
|
2
|
+
include API
|
3
|
+
LIMIT=1000.to_s
|
4
|
+
|
5
|
+
def self.get_all(bb,project)
|
6
|
+
data = bb.request(:get, "rest/api/1.0/projects/#{project}/repos/?limit=#{LIMIT}")
|
7
|
+
data['values'].map {|p| new(bb,p) }
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :bb, :name, :project
|
11
|
+
|
12
|
+
def initialize(server,data)
|
13
|
+
@bb = server
|
14
|
+
@name = data['slug']
|
15
|
+
@project = data['project']['key']
|
16
|
+
end
|
17
|
+
|
18
|
+
def branch_permissions
|
19
|
+
bb.request(:get, "rest/branch-permissions/2.0/projects/#{project}/repos/#{name}/restrictions")['values']
|
20
|
+
end
|
21
|
+
|
22
|
+
def master_branch_protected?
|
23
|
+
!! branch_permissions.detect do |c|
|
24
|
+
c["type"] == 'fast-forward-only' && c["matcher"]["id"] == 'refs/heads/master'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_master_branch_protected
|
29
|
+
perm = {
|
30
|
+
"type"=>"fast-forward-only",
|
31
|
+
"matcher"=>
|
32
|
+
{"id"=>"refs/heads/master",
|
33
|
+
"displayId"=>"master",
|
34
|
+
"type"=>{"id"=>"BRANCH", "name"=>"Branch"},
|
35
|
+
"active"=>true},
|
36
|
+
"users"=>[],
|
37
|
+
"groups"=>[]
|
38
|
+
}.to_json
|
39
|
+
bb.request(:post, "rest/branch-permissions/2.0/projects/#{project}/repos/#{name}/restrictions", perm)
|
40
|
+
end
|
41
|
+
|
42
|
+
def move_repo_to_project(new_project)
|
43
|
+
bb.request(:post, "/rest/api/1.0/projects/#{project}/repos/#{name}",{project: {key: new_project}}.to_json)
|
44
|
+
bb.request(:delete, "/rest/api/1.0/projects/#{project}/repos/#{name}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def hooks
|
48
|
+
bb.request(:get, "rest/api/1.0/projects/#{project}/repos/#{name}/settings/hooks")
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_hook_status(key, status)
|
52
|
+
if status
|
53
|
+
bb.request(:put, "rest/api/1.0/projects/#{project}/repos/#{name}/settings/hooks/#{key}/enabled")
|
54
|
+
else
|
55
|
+
bb.request(:delete, "rest/api/1.0/projects/#{project}/repos/#{name}/settings/hooks/#{key}/enabled")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def hook_settings(key)
|
60
|
+
bb.request(:get, "rest/api/1.0/projects/#{project}/repos/#{name}/settings/hooks/#{key}/settings")
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_hook_settings(key, settings)
|
64
|
+
bb.request(:put, "rest/api/1.0/projects/#{project}/repos/#{name}/settings/hooks/#{key}/settings", settings)
|
65
|
+
end
|
66
|
+
end
|
data/lib/config_store.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class ConfigStore
|
2
|
+
@@defaults_path = File.join(File.dirname(__FILE__),'../config/defaults.json')
|
3
|
+
@@overrides_path = File.expand_path('~/.config/api_deploy_overrides.json')
|
4
|
+
|
5
|
+
def self.defaults_path
|
6
|
+
@@defaults_path
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.defaults_path=(path)
|
10
|
+
@@defaults_path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.overrides_path
|
14
|
+
@@overrides_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.overrides_path=(path)
|
18
|
+
@@overrides_path = path
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load_config
|
22
|
+
warn "ConfigStoreuration overrides file not present at #{overrides_path}" unless File.exists?(overrides_path)
|
23
|
+
defaults = Hashie::Mash.new(JSON.parse(File.read(defaults_path))) if File.exists?(defaults_path)
|
24
|
+
overrides = JSON.parse(File.read(overrides_path)) if File.exists?(overrides_path)
|
25
|
+
defaults.deep_merge(overrides || {})
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def teamcity; @@ConfigStore.teamcity; end
|
30
|
+
def artifactory; @@ConfigStore.artifactory; end
|
31
|
+
def octopus; @@ConfigStore.octopus; end
|
32
|
+
def bitbucket; @@ConfigStore.bitbucket; end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.set_config
|
36
|
+
@@ConfigStore = self.load_config
|
37
|
+
end
|
38
|
+
|
39
|
+
self.set_config
|
40
|
+
end
|
data/lib/github.rb
ADDED
data/lib/ldap.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
class LdapApi
|
2
|
+
|
3
|
+
YOOX = {
|
4
|
+
name: 'YOOX',
|
5
|
+
host: 'ydcrootblq.yoox.net',
|
6
|
+
base: 'dc=yoox,dc=net',
|
7
|
+
port: 389,
|
8
|
+
user: ENV['YOOX_BIND_USER'],
|
9
|
+
pass: ENV['YOOX_BIND_PASS'],
|
10
|
+
}
|
11
|
+
NAP = {
|
12
|
+
name: 'LONDON',
|
13
|
+
host: 'RODC02-PR-IMO.london.net-a-porter.com',
|
14
|
+
base: 'dc=london,dc=net-a-porter,dc=com',
|
15
|
+
port: 389,
|
16
|
+
user: ENV['NAP_BIND_USER'],
|
17
|
+
pass: ENV['NAP_BIND_PASS'],
|
18
|
+
}
|
19
|
+
|
20
|
+
DOMAINS = [ YOOX, NAP ]
|
21
|
+
|
22
|
+
def domains
|
23
|
+
domain_structs = DOMAINS.map do |domain|
|
24
|
+
d = OpenStruct.new(
|
25
|
+
name: domain[:name],
|
26
|
+
user: domain[:user],
|
27
|
+
pass: domain[:pass],
|
28
|
+
ldap: Net::LDAP.new(
|
29
|
+
host: domain[:host],
|
30
|
+
port: domain[:port],
|
31
|
+
base: domain[:base],
|
32
|
+
auth: {
|
33
|
+
method: :simple,
|
34
|
+
username: domain[:user],
|
35
|
+
password: domain[:pass],
|
36
|
+
},
|
37
|
+
)
|
38
|
+
)
|
39
|
+
raise "BIND ERROR: #{domain}" unless d.ldap.bind
|
40
|
+
d
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def auth?(username, password)
|
45
|
+
domains.each do |domain|
|
46
|
+
domain.ldap.authenticate domain.name + "\\" + username, password
|
47
|
+
return true if domain.ldap.bind
|
48
|
+
end
|
49
|
+
return false
|
50
|
+
end
|
51
|
+
|
52
|
+
def groups(name)
|
53
|
+
filter = Net::LDAP::Filter.eq("sAMAccountName", name)
|
54
|
+
results = []
|
55
|
+
domains.map do |domain|
|
56
|
+
domain.ldap.search(filter: filter) do |entry|
|
57
|
+
results << entry.memberof.map {|e| e.sub(/^CN=/,'').sub(/,.*$/,'') }
|
58
|
+
end
|
59
|
+
domain.ldap.get_operation_result
|
60
|
+
end
|
61
|
+
results.flatten
|
62
|
+
end
|
63
|
+
|
64
|
+
def user(name)
|
65
|
+
filter = Net::LDAP::Filter.eq("sAMAccountName", name)
|
66
|
+
results = []
|
67
|
+
domains.map do |domain|
|
68
|
+
domain.ldap.search(filter: filter) do |entry|
|
69
|
+
results << entry
|
70
|
+
end
|
71
|
+
domain.ldap.get_operation_result
|
72
|
+
end
|
73
|
+
results.flatten
|
74
|
+
end
|
75
|
+
|
76
|
+
def group(name)
|
77
|
+
filter = Net::LDAP::Filter.eq("cn", name)
|
78
|
+
results = []
|
79
|
+
domains.map do |domain|
|
80
|
+
domain.ldap.search(filter: filter) do |entry|
|
81
|
+
results << entry
|
82
|
+
end
|
83
|
+
domain.ldap.get_operation_result
|
84
|
+
end
|
85
|
+
results.flatten
|
86
|
+
end
|
87
|
+
|
88
|
+
def in_group?(name, group)
|
89
|
+
groups(name).include?(group)
|
90
|
+
end
|
91
|
+
|
92
|
+
def users_in_group(group)
|
93
|
+
filter = Net::LDAP::Filter.eq("cn", group)
|
94
|
+
results = []
|
95
|
+
domains.map do |domain|
|
96
|
+
domain.ldap.search(filter: filter) do |entry|
|
97
|
+
results << entry.member.map {|e| user_from_name(e.sub(/^CN=/,'').sub(/,.*$/,'')) }
|
98
|
+
end
|
99
|
+
domain.ldap.get_operation_result
|
100
|
+
end
|
101
|
+
results.flatten
|
102
|
+
end
|
103
|
+
|
104
|
+
def user_from_name(name)
|
105
|
+
filter = Net::LDAP::Filter.eq("cn", name)
|
106
|
+
results = []
|
107
|
+
domains.map do |domain|
|
108
|
+
domain.ldap.search(filter: filter) do |entry|
|
109
|
+
results << entry[:samaccountname]
|
110
|
+
end
|
111
|
+
domain.ldap.get_operation_result
|
112
|
+
end
|
113
|
+
results.flatten
|
114
|
+
end
|
115
|
+
end
|
data/lib/libraries.rb
ADDED
data/lib/log.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class Log
|
2
|
+
class << self
|
3
|
+
def logger
|
4
|
+
@logger ||= ::Logging.logger(STDOUT)
|
5
|
+
@logger.level = ENV.fetch("LOG_LEVEL", "warn").to_sym
|
6
|
+
@logger
|
7
|
+
end
|
8
|
+
def warn(m); logger.warn(m); end
|
9
|
+
def info(m); logger.info(m); end
|
10
|
+
def debug(m); logger.debug(m); end
|
11
|
+
def level=(l); logger.level = l; end
|
12
|
+
end
|
13
|
+
end
|
data/lib/octopus_api.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
class OctopusApi
|
2
|
+
include API
|
3
|
+
|
4
|
+
RESOURCE_TYPES = ['Environments','Projects','ProjectGroups','NugetFeeds','LibraryVariableSets','Machines','Lifecycles','Users','Releases','Deployments']
|
5
|
+
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
create_api(ConfigStore.octopus)
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_resource(type, query)
|
12
|
+
check_type type
|
13
|
+
request(:post, "/#{type}", query)
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove_resource(type, id)
|
17
|
+
check_type type
|
18
|
+
request(:delete, "/#{type}/#{id}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_resource(type)
|
22
|
+
check_type type
|
23
|
+
return request(:get, "/#{type}/all")
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource_exists?(type, name)
|
27
|
+
resource = get_resource_by_type_and_name(type, name)
|
28
|
+
return (resource && resource != [])
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_resource_by_type_and_name(type, name = nil)
|
32
|
+
resources = get_resource(type)
|
33
|
+
if name && name != ''
|
34
|
+
filter = [*name].join("|")
|
35
|
+
|
36
|
+
filtered_resources = resources.select do |resource|
|
37
|
+
resource['Name'] =~ /#{filter}/
|
38
|
+
end
|
39
|
+
|
40
|
+
if filtered_resources.any?
|
41
|
+
Log.info "#{filtered_resources.count} resources found with filter #{filter}"
|
42
|
+
filtered_resources
|
43
|
+
else
|
44
|
+
Log.info "No #{type} found with filter #{filter}"
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
else
|
48
|
+
resources
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_type(type)
|
53
|
+
raise NameError, "Invalid resource type supplied: '#{type}'" unless RESOURCE_TYPES.include? type
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
data/lib/teamcity_api.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class TeamcityApi
|
2
|
+
include API
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
create_api( ConfigStore.teamcity )
|
6
|
+
end
|
7
|
+
|
8
|
+
def build_queue
|
9
|
+
request(:get, 'buildQueue')
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_build(build_config_id, properties={})
|
13
|
+
properties_string = ""
|
14
|
+
properties.each_pair do |k,v|
|
15
|
+
properties_string << "<property name='#{k}' value='#{v}'/>\n"
|
16
|
+
end
|
17
|
+
data = "
|
18
|
+
<build>
|
19
|
+
<buildType id='#{build_config_id}'/>
|
20
|
+
<properties>
|
21
|
+
#{properties_string}
|
22
|
+
</properties>
|
23
|
+
</build>
|
24
|
+
"
|
25
|
+
request(:post, 'buildQueue', data, "xml")
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_project_parameter(project_id,parameter,value)
|
29
|
+
request(:put, "projects/#{project_id}/parameters/#{parameter}", value, "text")
|
30
|
+
end
|
31
|
+
|
32
|
+
def projects(parent_id=nil)
|
33
|
+
list = Nokogiri::XML.parse(request(:get, 'projects').body)
|
34
|
+
if parent_id
|
35
|
+
(list.xpath '//project').select {|e| e.attributes['parentProjectId'].to_s == parent_id}
|
36
|
+
else
|
37
|
+
list
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|