Revision 4e7b2b98
Added by Paul Kelly over 13 years ago
- ID 4e7b2b980ef82f3918c6ac07f4e7f1ceb17c4a06
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
Fixes #450 - Environment imports lose their puppetclass associations