api_deploy 0.1.0
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.
- 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
|