Class: Sandbox

Inherits:
Object
  • Object
show all
Includes:
ReleaseManager::Logger
Defined in:
lib/release_manager/sandbox.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ReleaseManager::Logger

#color, #log_level, #logger

Constructor Details

#initialize(name, modules, control_repo_path, repos_dir = nil, options = {}) ⇒ Sandbox

Returns a new instance of Sandbox.



15
16
17
18
19
20
21
22
# File 'lib/release_manager/sandbox.rb', line 15

def initialize(name, modules, control_repo_path, repos_dir = nil, options = {})
  @name = name
  @repos_dir = repos_dir
  @module_names = modules
  @control_repo_path = control_repo_path
  @vcs = ReleaseManager::VCSManager.default_instance
  @options = options
end

Instance Attribute Details

#control_repoControlRepo (readonly)

Returns - a ControlRepo object.

Returns:



43
44
45
# File 'lib/release_manager/sandbox.rb', line 43

def control_repo
  @control_repo
end

#control_repo_pathString (readonly)

Returns the r10k control repo path, defaults to ~/repos/r10k-control.

Returns:

  • (String)

    the r10k control repo path, defaults to ~/repos/r10k-control



38
39
40
# File 'lib/release_manager/sandbox.rb', line 38

def control_repo_path
  @control_repo_path
end

#module_namesObject (readonly)

Returns the value of attribute module_names.



10
11
12
# File 'lib/release_manager/sandbox.rb', line 10

def module_names
  @module_names
end

#modulesObject (readonly)



237
238
239
# File 'lib/release_manager/sandbox.rb', line 237

def modules
  @modules
end

#nameObject (readonly)

Returns the value of attribute name.



10
11
12
# File 'lib/release_manager/sandbox.rb', line 10

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



10
11
12
# File 'lib/release_manager/sandbox.rb', line 10

def options
  @options
end

#repos_dirString (readonly)

Returns the repos_path, defaults to ~/repos.

Returns:

  • (String)

    the repos_path, defaults to ~/repos



33
34
35
# File 'lib/release_manager/sandbox.rb', line 33

def repos_dir
  @repos_dir
end

#vcsObject (readonly)

Returns the value of attribute vcs.



10
11
12
# File 'lib/release_manager/sandbox.rb', line 10

def vcs
  @vcs
end

Class Method Details

.create(name, options) ⇒ Object

the user passes in the r10k git url or path or we assume the path if the user does not pass in the git url we assume the repo already exist if the repo doesn’t exist we clone the url

Parameters:

  • name (String)
    • the name of the sandbox

  • Array[String] (Array[String] modules - the names of the modules that should be forked and branched)

    modules - the names of the modules that should be forked and branched

  • repos_dir (String)
    • the path to the repos directory

  • control_repo_path (String)
    • the url or path to the r10k control repo

  • [String] (Hash)

    a customizable set of options



263
264
265
266
267
268
269
270
# File 'lib/release_manager/sandbox.rb', line 263

def self.create(name, options)
  box = Sandbox.new(name, options[:modules],
                    options[:r10k_repo_path],
                    options[:repos_path], options)
  box.check_requirements
  box.verify_api_token
  box.create(options[:r10k_repo_url])
end

Instance Method Details

#check_requirementsObject



245
246
247
248
249
250
251
252
# File 'lib/release_manager/sandbox.rb', line 245

def check_requirements
  begin
    vcs.add_ssh_key
  rescue InvalidModuleNameException => e
    logger.error(e.message)
    exit 1
  end
end

#clone_repo(mod_name, url) ⇒ Object

TODO: extract this out to an adapter



231
232
233
234
# File 'lib/release_manager/sandbox.rb', line 231

def clone_repo(mod_name, url)
  path = File.join(repos_dir, mod_name)
  Rugged::Repository.clone_at(url, path, checkout_branch: name)
end

#create(r10k_url) ⇒ Object

checkout and/or create branch get modules fork module unless already exists clone fork of module create branch of fork set module fork set module branch set upstream to original namespace cleanup branches



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/release_manager/sandbox.rb', line 135

def create(r10k_url)
  setup_repos_dir(repos_dir)
  @control_repo = setup_control_repo(r10k_url)
  # get modules we are interested in
  module_names.each do | mod_name |
    puts "## #{mod_name} ##".yellow
    begin
      mod = puppetfile.find_mod(mod_name)
      setup_module_repo(mod)
    rescue InvalidModuleNameException => e
      logger.error(e.message)
      value = nil
      loop do
        print "Do you want to create a new entry in the Puppetfile for the module named #{mod_name}?(y/n): ".yellow
        value = gets.downcase.chomp
        break if value =~ /y|n/
      end
      next if value == 'n'
      mod = setup_new_module(mod_name)
      setup_module_repo(mod)
    end
  end
  @control_repo.checkout_branch(name)
  puppetfile.write_to_file
  logger.info("Committing Puppetfile changes to r10k-control branch: #{name}")
  puppetfile.commit("Sandbox Creation for #{name} environment")
  logger.info("Pushing new environment branch: #{name} to upstream")
  puppetfile.push('upstream', name, true)
  return self
end

#create_repo_branch(repo_id, branch_name) ⇒ Object

TODO: extract this out to an adapter

Returns:

  • String - the branch name that was created



226
227
228
# File 'lib/release_manager/sandbox.rb', line 226

def create_repo_branch(repo_id, branch_name)
  Gitlab.repo_create_branch(repo_id, branch_name)
end

#create_repo_fork(url, namespace = nil) ⇒ Gitlab::ObjectifiedHash

TODO: extract this out to an adapter

Parameters:

Returns:

  • (Gitlab::ObjectifiedHash)

    Information about the forked project



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/release_manager/sandbox.rb', line 195

def create_repo_fork(url, namespace = nil )
  new_url = swap_namespace(url, namespace)
  repo = repo_exists?(new_url)
  unless repo
    upstream_repo_id = repo_id(url)
    logger.info("Forking project from #{url} to #{new_url}")
    repo = Gitlab.create_fork(upstream_repo_id)
    # gitlab lies about having completed the forking process, so lets sleep until it is actually done
    loop do
      sleep(1)
      break if repo_exists?(repo.ssh_url_to_repo)
    end
  end
  vcs.add_permissions(repo.id, options[:default_members])
  repo
end

#puppetfileObject



241
242
243
# File 'lib/release_manager/sandbox.rb', line 241

def puppetfile 
  @puppetfile ||= control_repo.puppetfile
end

#repo_exists?(url) ⇒ Boolean

TODO: extract this out to an adapter

Parameters:

  • url (String)
    • the git url of the repository

Returns:

  • (Boolean)

    returns the project object (true) if found, false otherwise



215
216
217
218
219
220
221
222
# File 'lib/release_manager/sandbox.rb', line 215

def repo_exists?(url)
  upstream_repo_id = repo_id(url)
  begin
    Gitlab.project(upstream_repo_id)
  rescue
    false
  end
end

#repo_id(url) ⇒ String

gets the project id from gitlab using the remote API

Parameters:

Returns:

  • (String)

    a string representing the project id from gitlab

Raises:



169
170
171
172
173
174
175
# File 'lib/release_manager/sandbox.rb', line 169

def repo_id(url)
  # ie. git@server:namespace/project.git
  proj = url.match(/:(.*\/.*)\.git/)
  raise RepoNotFound unless proj
  # the gitlab api is supposed to encode the slash, but currently that doesn't seem to work
  proj[1].gsub('/', '%2F')
end

#setup_control_repo(url) ⇒ ControlRepo

Returns - creates a new control repo object and clones the url unless already cloned.

Parameters:

  • url (String)
    • the url to clone and fork

Returns:

  • (ControlRepo)
    • creates a new control repo object and clones the url unless already cloned



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/release_manager/sandbox.rb', line 49

def setup_control_repo(url)
  # clone r10k unless already cloned
  puts "## r10k-control ##".yellow
  fork = create_repo_fork(url)
  c = ControlRepo.create(control_repo_path, fork.ssh_url_to_repo)
  c.add_remote(fork.ssh_url_to_repo, 'myfork')
  c.fetch('myfork')
  c.fetch('origin')
  c.add_remote(url, 'upstream')
  # if the user doesn't have the branch, we create from upstream
  # and then checkout from the fork, we defer pushing the branch to later after updating the puppetfile
  target = c.branch_exist?("upstream/#{name}") ? "upstream/#{name}" : 'upstream/dev'
  # if the user has previously created the branch but doesn't exist locally, no need to create
  c.create_branch(name, target)
  c.checkout_branch(name)
  c
end

#setup_module_repo(mod) ⇒ PuppetModule

if the fork is already created, do nothing

Parameters:

  • mod (ControlMod)
    • the module to clone and fork

  • create_fork (Boolean)
    • defaults to true which creates a fork

Returns:

  • (PuppetModule)
    • creates a new puppet_module object and clones the url unless already cloned

Raises:



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/release_manager/sandbox.rb', line 71

def setup_module_repo(mod)
  raise InvalidModule.new(mod) unless mod.instance_of?(ControlMod)
  fork = create_repo_fork(mod.repo)
  m = PuppetModule.create(File.join(repos_dir, mod.name), fork.ssh_url_to_repo, name)
  m.fetch('origin')
  m.add_remote(fork.ssh_url_to_repo, 'myfork')
  # without the following, we risk accidently setting the upstream to the newly forked url
  # this occurs because r10k-control branch contains the forked url instead of the upstream url
  # we assume the metadata.source attribute contains the correct upstream url
  begin
    delay_source_change = false
    if m.source =~ /\Agit\@/
      m.add_remote(m.source, 'upstream', true)
    else
      logger.warn("Module's source is not defined correctly for #{m.name} should be a git url, fixing...")
      # delay the changing of metadata source until we checkout the branch
      delay_source_change = true
      m.add_remote(mod.repo, 'upstream', true)
    end
  rescue ModNotFoundException => e
    logger.error("Is #{mod.name} a puppet module?  Can't find the metadata source")
  end
  # if the user doesn't have the branch, we create from upstream
  # and then checkout from the fork
  # if the user has previously created the branch but doesn't exist locally, no need to create
  if m.remote_exists?('upstream')
    target = m.branch_exist?("myfork/#{name}") ? "myfork/#{name}" : 'upstream/master'
  else
    # don't create from upstream since the upstream remote does not exist
    # upstream does not exist because the url in the metadata source is not a git url
    target = 'master'
  end
  m.create_branch(name, target)
  m.push_branch('myfork', name)
  m.checkout_branch(name)
  if delay_source_change
    m.source = mod.repo
    m.
  end
  logger.info("Updating r10k-control Puppetfile to use fork: #{fork.ssh_url_to_repo} with branch: #{name}")
  puppetfile.write_source(mod.name, fork.ssh_url_to_repo, name )
  m
end

#setup_new_module(mod_name) ⇒ Object



115
116
117
118
119
120
121
122
123
124
# File 'lib/release_manager/sandbox.rb', line 115

def setup_new_module(mod_name)
  repo_url = nil
  loop do
    print "Please enter the git url of the source repo : ".yellow
    repo_url = gets.chomp
    break if repo_url =~ /git\@/
    puts "Repo Url must be a git url".red
  end
  puppetfile.add_module(mod_name, git: repo_url)
end

#setup_repos_dir(repos_path) ⇒ String

Creates the repos path using mkdir_p unless the path already exists

Parameters:

  • repos_path (String)
    • the path to the repos directory where you want to clone modules

Returns:



27
28
29
30
# File 'lib/release_manager/sandbox.rb', line 27

def setup_repos_dir(repos_path)
  FileUtils.mkdir_p(repos_path) unless File.exists?(repos_path)
  repos_path
end

#swap_namespace(url, namespace = nil) ⇒ Object

TODO: extract this out to an adapter replaces namespace from the url with the supplied or default namespace



188
189
190
# File 'lib/release_manager/sandbox.rb', line 188

def swap_namespace(url, namespace = nil)
  url.gsub(/\:([\w-]+)\//, ":#{namespace || Gitlab.user.username}/")
end

#verify_api_tokenObject

TODO: extract this out to an adapter



178
179
180
181
182
183
184
# File 'lib/release_manager/sandbox.rb', line 178

def verify_api_token
  begin
    Gitlab.user
  rescue Exception => e
    raise InvalidToken.new(e.message)
  end
end