Project

General

Profile

« Previous | Next » 

Revision 4e7b2b98

Added by Paul Kelly over 13 years ago

  • ID 4e7b2b980ef82f3918c6ac07f4e7f1ceb17c4a06

Fixes #450 - Environment imports lose their puppetclass associations

View differences:

app/models/environment.rb
end
# Update the environments and puppetclasses based upon the user's selection
# It does a best attempt and can fail to perform all operations due to the
# user requesting impossible selections. Repeat the operation if errors are
# shown, after fixing the request.
# +changed+ : Hash with two keys: :new and :obsolete.
# changed[:/new|obsolete/] is and Array of Strings
# Returns : String containing all record error strings joined with <br/>
# Returns : Array of Strings containing all record errors
def self.obsolete_and_new changed
changed ||= {}
# Fill in any missing bits in the changed datastructure
changed.reverse_merge! :new => {}, :obsolete => {}
changed[:new].reverse_merge!(:environments => [], :puppetclasses => []);changed[:obsolete].reverse_merge!(:environments => [], :puppetclasses => [])
@import_errors = []
# First we create any new puppetclasses
for pclass in changed[:new][:puppetclasses]
Puppetclass.find_or_create_by_name pclass
pc = Puppetclass.find_or_create_by_name pclass
@import_errors += pc.errors unless pc.errors.empty?
end
errors = ""
# Then we create any new environments and attach the puppetclasses
for env, paths in self.puppetEnvs
if changed[:new][:environments].include? env.to_s
env = Environment.find_or_create_by_name env.to_s
for path in paths.split ":"
pcs = Array.new
for modulepath in path.split(":")
pcs = Puppetclass.scanForClasses(modulepath)
# We do not bind classes to be deleted to the new environment
pcs -= changed[:obsolete][:puppetclasses]
env.puppetclasses = pcs.map do |pc|
if (theClass = Puppetclass.find_by_name(pc)).nil?
errors += "Unable to find puppetclass #{pc} to attach to #{env.name}<br/>"
end
theClass
end.compact
end
puppet_envs = puppetEnvs
# Then we create any new environments and add the associations
for env_str in changed[:new][:environments]
env = Environment.find_or_create_by_name env_str
if (env.valid? and ! env.new_record?)
pcs = Puppetclass.scanForClasses(puppet_envs[env_str.to_sym]) - changed[:obsolete][:puppetclasses]
env.puppetclasses = names_to_instances(pcs, env)
env.save!
else
@import_errors << "Unable to find or create environment #{env_str} in the foreman database"
end
end
# We rebuild the puppetclass bindings for all the environments known by foreman minus the ones we will delete
for env_str in Environment.all.map(&:name) - changed[:obsolete][:environments]
if (env = Environment.find_by_name(env_str))
if (path = puppet_envs[env_str.to_sym])
pcs = Puppetclass.scanForClasses(path) - changed[:obsolete][:puppetclasses]
# Convert the strings back into classes and add as an association
env.puppetclasses = names_to_instances pcs, env
env.save!
else
@import_errors << "Unable to find the module paths for environment #{env_str}. This is OK if you blocked its deletion."
end
else
@import_errors << "Unable to rebuild environment #{env_str}. It is not in the foreman database"
end
end
# We now delete the obsolete puppetclasses
for pclass in changed[:obsolete][:puppetclasses]
(pc = Puppetclass.find_by_name(pclass)).destroy
pc.errors.each_full {|msg| errors += "#{msg}<br/>"} unless pc.errors.empty?
# Now we delete the obsolete environments
for env_str in changed[:obsolete][:environments]
if (env = Environment.find_by_name env_str)
env.puppetclasses.clear
env.destroy
@import_errors += env.errors.full_messages unless env.errors.empty?
else
@import_errors << "Unable to delete environment #{env_str}. It is not in the foreman database"
end
end
# Now finally remove the old environements
for environment in changed[:obsolete][:environments]
(env = Environment.find_by_name(environment)).destroy
env.errors.each_full {|msg| errors += "#{msg}<br/>"} unless env.errors.empty?
# and finally delete the obsolete puppetclasses
for pclass in changed[:obsolete][:puppetclasses]
if (pc = Puppetclass.find_by_name(pclass))
pc.destroy
@import_errors += pc.errors.full_messages unless pc.errors.empty?
else
@import_errors << "Unable to delete puppetclass #{pclass}. It is not in the foreman database"
end
end
errors
@import_errors
end
def as_json(options={})
super({:only => [:name, :id]}.merge(options))
end
private
def self.names_to_instances pcs, env
pcs.map do |pc|
if (theClass = Puppetclass.find_by_name(pc)).nil?
@import_errors << "Unable to add puppetclass '#{pc}' to #{env.name}. This is OK if you have disabled its creation'"
end
theClass
end.compact
end
end
app/models/puppetclass.rb
before_destroy Ensure_not_used_by.new(:hosts)
before_destroy Ensure_not_used_by.new(:hostgroups)
# Scans a directory for puppet classes
# +path+ : String containing a single module directory
# Scans a directory path for puppet classes
# +paths+ : String containing a colon separated module path
# returns
# Array of Strings containing puppetclass names
def self.scanForClasses(path)
def self.scanForClasses(paths)
klasses=Array.new
Dir.glob("#{path}/*/manifests/**/*.pp").each do |manifest|
File.read(manifest).each_line do |line|
klass=line.match(/^class (\S+).*\{/)
klasses << klass[1] if klass
for path in paths.split(":")
Dir.glob("#{path}/*/manifests/**/*.pp").each do |manifest|
File.read(manifest).each_line do |line|
klass=line.match(/^class (\S+).*\{/)
klasses << klass[1] if klass
end
end
end
return klasses
return klasses.uniq
end
# returns a hash containing modules and associated classes
app/views/common/_puppetclasses_or_envs_changed.html.erb
<fieldset>
<legend><%= kind.to_s.capitalize -%> puppetclasses</legend>
<% unless (pcs = @changed.send(:[], kind)[:puppetclasses]).empty? -%>
<table>
<% pcs.in_groups_of(@grouping) do |group| -%>
<tr>
<% for i in 0...@grouping do %>
<% if group[i] -%>
<td>
<%= check_box_tag "changed[#{kind.to_s}][puppetclasses][]", group[i], true %>
<%= group[i] -%>
</td>
<% end -%>
<table>
<% pcs.in_groups_of(@grouping) do |group| -%>
<tr>
<% for i in 0...@grouping do %>
<% if group[i] -%>
<td>
<%= check_box_tag "changed[#{kind.to_s}][puppetclasses][]", group[i], true %>
<%= group[i] -%>
</td>
<% end -%>
<% end -%>
</tr>
<% end -%>
</tr>
<% end -%>
</table>
</table>
<% end -%>
</fieldset>
<fieldset>
<legend><%= kind.to_s.capitalize -%> environments</legend>
<% unless (envs = @changed.send(:[], kind)[:environments]).empty? -%>
<table>
<% envs.in_groups_of(@grouping) do |group| -%>
<tr>
<% for i in 0...@grouping do %>
<% if group[i] -%>
<td>
<%= check_box_tag "changed[#{kind.to_s}][environments][]", group[i], true %>
<%= group[i] -%>
</td>
<% end -%>
<table>
<% envs.in_groups_of(@grouping) do |group| -%>
<tr>
<% for i in 0...@grouping do %>
<% if group[i] -%>
<td>
<%= check_box_tag "changed[#{kind.to_s}][environments][]", group[i], true %>
<%= group[i] -%>
</td>
<% end -%>
<% end -%>
</tr>
<% end -%>
</tr>
<% end -%>
</table>
</table>
<% end -%>
</fieldset>
<% end -%>
<div>
<%= submit_tag "Update" -%> <%= submit_tag "Cancel" -%>
<%= submit_tag "Update" -%> or <%= link_to "Cancel", environments_path -%>
</div>
<% end -%>
<% end -%>
lib/access_permissions.rb
map.permission :create_environments, {:environments => [:new, :create]}
map.permission :edit_environments, {:environments => [:edit, :update]}
map.permission :destroy_environments, {:environments => [:destroy]}
map.permission :import_environments, {:environments => [:import_environments]}
map.permission :import_environments, {:environments => [:import_environments, :obsolete_and_new]}
end
map.security_block :external_variables do |map|
lib/foreman/controller/environments.rb
end
def obsolete_and_new
if params[:commit] == "Cancel"
redirect_to environments_path
if (errors = Environment.obsolete_and_new(params[:changed])).empty?
flash[:foreman_notice] = "Succcessfully updated environments and puppetclasses from the on-disk puppet installation"
else
if (errors = Environment.obsolete_and_new(params[:changed])).empty?
flash[:foreman_notice] = "Succcessfully updated environments and puppetclasses from the on-disk puppet installation"
else
flash[:foreman_error] = "Failed to update the environments and puppetclasses from the on-disk puppet installation<br/>" + errors
end
redirect_to :back
flash[:foreman_error] = "Failed to update the environments and puppetclasses from the on-disk puppet installation<br/>" + errors.join("<br>")
end
redirect_to puppetclasses_path
end
end
test/functional/environments_controller_test.rb
test "should update environments via obsolete_and_new" do
@request.env["HTTP_REFERER"] = environments_url
Environment.create :name => "tester"
["a", "b", "c"].each {|name| Puppetclass.create :name => name}
Environment.expects(:puppetEnvs).returns(:muc => "/etc/puppet/env/muc",
:global_puppetmaster => "/etc/puppet/env/global_puppetmaster:/etc/puppet/modules/sites",
as_admin do
["tester", "muc"].each {|name| Environment.create :name => name}
["a", "b", "c"].each {|name| Puppetclass.create :name => name}
end
Environment.expects(:puppetEnvs).returns(:tester => "/etc/puppet/env/muc",
:muc => "/etc/puppet/env/muc",
:dog => "/etc/puppet/env/global_puppetmaster:/etc/puppet/modules/sites"
).at_least_once
Puppetclass.expects(:scanForClasses).returns(["a", "b", "c"]).at_least_once
......
test "should update puppetclasses via obsolete_and_new" do
@request.env["HTTP_REFERER"] = environments_url
Puppetclass.create :name => "tester"
as_admin do
["tester", "base"].each {|name| Puppetclass.create :name => name}
["muc", "dog"].each {|name| Environment.create :name => name}
end
Environment.expects(:puppetEnvs).returns(:muc => "/etc/puppet/env/muc",
:global_puppetmaster => "/etc/puppet/env/global_puppetmaster:/etc/puppet/modules/sites",
:dog => "/etc/puppet/env/global_puppetmaster:/etc/puppet/modules/sites"
).at_least_once
Puppetclass.expects(:scanForClasses).returns(["base", "cat", "tester"]).at_least_once
post :obsolete_and_new, { "changed"=>{
"obsolete" => {"puppetclasses" => ["tester"]},
"new" => {"puppetclasses" => ["cat"] }
......
assert flash[:foreman_notice] = "Succcessfully updated environments and puppetclasses from the on-disk puppet installation"
assert_nil Puppetclass.find_by_name("tester")
assert Puppetclass.find_by_name("cat")
assert Environment.find_by_name("muc").puppetclasses == [Puppetclass.find_by_name("base"), Puppetclass.find_by_name("cat")]
assert Environment.find_by_name("dog").puppetclasses == [Puppetclass.find_by_name("base"), Puppetclass.find_by_name("cat")]
assert Environment.find_by_name("global_puppetmaster").puppetclasses == [Puppetclass.find_by_name("base"), Puppetclass.find_by_name("cat")]
end
test "should fail to remove active environments" do
@request.env["HTTP_REFERER"] = environments_url
Environment.expects(:puppetEnvs).returns(:production => "/etc/puppet/env/muc",
Environment.expects(:puppetEnvs).returns(:dummy => "/etc/puppet/env/muc",
:global_puppetmaster => "/etc/puppet/env/global_puppetmaster:/etc/puppet/modules/sites"
).at_least_once
Puppetclass.expects(:scanForClasses).returns(["a", "b", "c"]).at_least_once
host = hosts(:myfullhost)
host.environment = Environment.find_by_name("production")
host.domain = Domain.find_or_create_by_name("mydomain.com")
assert host.save
assert Host.find_by_name("myfullname.mydomain.com").environment == Environment.find_by_name("production")
as_admin do
host.environment = Environment.find_or_create_by_name("dummy")
host.domain = Domain.find_or_create_by_name("mydomain.com")
assert host.save
end
assert Host.find_by_name("myfullname.mydomain.com").environment == Environment.find_by_name("dummy")
post :obsolete_and_new, { "changed"=>{
"obsolete" => {"environments" => ["production"]}
"new" => {"puppetclasses" => ["a", "b", "c"]},
"obsolete" => {"environments" => ["dummy"]}
}}, set_session_user
assert flash[:foreman_error] =~ /^Failed to update the environments and puppetclasses from the on-disk puppet installation/
assert Environment.find_by_name("production")
assert Environment.find_by_name("dummy")
end
test "should fail to remove active puppetclasses" do
test/functional/hosts_controller_test.rb
end
test "should render 404 when host is not found" do
get :show, :id => "no.such.host"
get :show, {:id => "no.such.host"}, set_session_user
assert_response :missing
assert_template 'common/404'
end

Also available in: Unified diff