foreman_git_templates 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f37d8fd9a09c475e1e544c46d1d17d52b928d1dbdb8f37a77c83e4e45aafb026
4
- data.tar.gz: 4dca8880532ee1fcd6d5b28d45896bd922505841e68af7945e2115f3b51de16a
3
+ metadata.gz: ef1542116b039a0e33811e4eaa3c65b3c05da618735f47f9f30fe5dd9b243599
4
+ data.tar.gz: fbbd7adb1894aac45862a92653cc2e84667a1ea14a19c2a1e17ab44d882e6358
5
5
  SHA512:
6
- metadata.gz: 403f6ec203c8d2f1a9267249f9107d3323ec24c0e68a23420ec8f89b3a6d75fb091d25931fc2de6785b3b635c91ac97a6d48435a4678ff799f0968dee598e51f
7
- data.tar.gz: e13f00738e2878f7f7f102e9b5d4df745e3b54caa3aba7afe1c82b9ebd297415b4c5f9b634ddb0cb6b9764737acf60d76951d58f77aec261fa1eb76e86466397
6
+ metadata.gz: 3ba5ffceb6a9049d268589824b73bcf9c1d135d05db12242e08473abe3a2aa47fe4aa21129baf27e646e42d1ce7cddad8d7272b23d173c8a3bb82a75d64dec43
7
+ data.tar.gz: 9539d07fd4eee73f05d467a5a5f3e2b83d3ff34337b155a96913c53cc69006b25840010d07df79b90ae2f0d219a986e341d094bc4c06b3b80b89525d28938fab
data/README.md CHANGED
@@ -44,6 +44,14 @@ From now the template content for this host will be fetched from the repository.
44
44
  - `There was an error rendering the provision template: Cannot read <template> from repository`
45
45
  - check if there is requested template in the repository
46
46
 
47
+ ### Gitlab Repository Integration
48
+
49
+ If you want to use a private repository hosted on a Gitlab instance to store your Foreman templates, you can use [Gitlab's repositories API](https://docs.gitlab.com/ee/api/repositories.html#get-file-archive) to construct the `template_url` parameter in Foreman. Create a dedicated Foreman user in Gitlab and set up a Personal Access Token that you can use in the `template_url`.
50
+
51
+ ```
52
+ https://gitlab.example.com/api/v4/projects/${GITLAB_PROJECT_ID}/repository/archive.tar.gz?sha=${GITLAB_BRANCH_NAME}&private_token=${PERSONAL_ACCESS_TOKEN}
53
+ ```
54
+
47
55
  ## Contributing
48
56
 
49
57
  Fork and send a Pull Request. Thanks!
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module UnattendedControllerExtensions
5
+ extend ActiveSupport::Concern
6
+
7
+ module Overrides
8
+ def render_local_boot_template
9
+ return super unless @host&.repository_path
10
+
11
+ return unless verify_found_host
12
+
13
+ template = ForemanGitTemplates::DefaultLocalBootRepositoryTemplate.new(name: 'iPXE')
14
+ safe_render(template)
15
+ rescue ForemanGitTemplates::RepositoryReader::MissingFileError
16
+ render_ipxe_message(message: _('iPXE default local boot template not found in repository at templates/iPXE/default_local_boot.erb'))
17
+ end
18
+ end
19
+
20
+ included do
21
+ prepend Overrides
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module ApplicationHelper
5
+ extend ActiveSupport::Concern
6
+
7
+ module Overrides
8
+ def display_link_if_authorized(name, options = {}, html_options = {})
9
+ # rubocop:disable Rails/HelperInstanceVariable
10
+
11
+ return if @host&.repository_path && options[:use_route] == 'edit_provisioning_template'
12
+
13
+ # rubocop:enable Rails/HelperInstanceVariable
14
+
15
+ super
16
+ end
17
+ end
18
+
19
+ included do
20
+ prepend Overrides
21
+ end
22
+ end
23
+ end
@@ -2,14 +2,22 @@
2
2
 
3
3
  module ForemanGitTemplates
4
4
  module Renderer
5
- def get_source(klass: Foreman::Renderer::Source::Database, template:, **args)
5
+ REPOSITORY_SOURCE_CLASS = ForemanGitTemplates::Renderer::Source::Repository
6
+
7
+ # rubocop:disable Metrics/PerceivedComplexity
8
+ def get_source(klass: nil, template:, **args)
9
+ return super if klass && klass != REPOSITORY_SOURCE_CLASS
10
+
6
11
  repository_path = repository_path(args[:host])
7
12
  if repository_path
8
- ForemanGitTemplates::Renderer::Source::Repository.new(template, repository_path)
13
+ REPOSITORY_SOURCE_CLASS.new(template, repository_path)
14
+ elsif !repository_path && Gem::Version.new(SETTINGS[:version].version) < Gem::Version.new('1.23')
15
+ super(klass: klass || Foreman::Renderer::Source::Database, template: template, **args)
9
16
  else
10
17
  super
11
18
  end
12
19
  end
20
+ # rubocop:enable Metrics/PerceivedComplexity
13
21
 
14
22
  private
15
23
 
@@ -2,10 +2,35 @@
2
2
 
3
3
  module ForemanGitTemplates
4
4
  module HostExtensions
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ validates_associated :host_parameters
9
+ end
10
+
11
+ def repository_klass
12
+ build? ? MainRepositoryTemplate : DefaultLocalBootRepositoryTemplate
13
+ end
14
+
5
15
  def repository_path
6
- return unless host_params['template_url']
16
+ return unless git_template_url
17
+
18
+ git_template_tmpfile&.path
19
+ end
20
+
21
+ private
22
+
23
+ def git_template_url
24
+ @git_template_url ||= host_params['template_url']
25
+ end
26
+
27
+ def git_template_tmpfile
28
+ return unless git_template_url
7
29
 
8
- @repository_path ||= RepositoryFetcher.call(host_params['template_url'])
30
+ @git_template_tmpfile ||= RepositoryFetcher.call(git_template_url)
31
+ rescue ::ForemanGitTemplates::RepositoryFetcher::RepositoryFetcherError => e
32
+ Foreman::Logging.exception("GitTemplates: Failed to fetch data from #{git_template_url}", e)
33
+ nil
9
34
  end
10
35
  end
11
36
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module HostParameterExtensions
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ validate :template_url_is_reachable, if: ->(p) { p.name == 'template_url' }
9
+ end
10
+
11
+ private
12
+
13
+ def template_url_is_reachable
14
+ RestClient::Request.execute(method: :head, url: value, timeout: Setting::GitTemplates['template_url_validation_timeout'])
15
+ rescue RestClient::ExceptionWithResponse, URI::InvalidURIError, SocketError => e
16
+ errors.add(:value, _('Cannot fetch templates from %{url}: %{error}') % { url: value, error: e.message })
17
+ end
18
+ end
19
+ end
@@ -9,7 +9,7 @@ module ForemanGitTemplates
9
9
  def provisioning_template(opts = {})
10
10
  return super unless repository_path
11
11
 
12
- kind = opts[:kind] || 'provision'
12
+ kind = opts[:kind].to_s || 'provision'
13
13
  available_template_kinds.find { |template| template.name == kind }
14
14
  end
15
15
 
@@ -17,8 +17,8 @@ module ForemanGitTemplates
17
17
  return super unless repository_path
18
18
 
19
19
  @available_template_kinds ||= template_kinds(provisioning).map do |kind|
20
- MainRepositoryTemplate.new(name: kind.name).tap do |template|
21
- RepositoryReader.call(repository_path, template.path)
20
+ repository_klass.new(name: kind.name).tap do |t|
21
+ t.template = RepositoryReader.call(repository_path, t.path)
22
22
  end
23
23
  rescue RepositoryReader::FileUnreadableError # file is missing or empty
24
24
  next
@@ -9,16 +9,26 @@ module ForemanGitTemplates
9
9
  delegate :render_template, to: :host
10
10
 
11
11
  def generate_pxe_template(kind)
12
- return super unless host.repository_path
12
+ return super unless host.params['template_url']
13
13
 
14
- template_klass = build? ? MainRepositoryTemplate : DefaultLocalBootRepositoryTemplate
15
- template = template_klass.new(name: kind)
14
+ template = host.repository_klass.new(name: kind)
16
15
  render_template(template: template)
17
- rescue ForemanGitTemplates::RepositoryReader::MissingFileError => e
18
- # re-raise the exception if we have a main template defined for this type
19
- raise e if host.available_template_kinds.map(&:name).include?(kind)
16
+ rescue ForemanGitTemplates::RepositoryReader::MissingFileError
20
17
  nil
21
18
  end
19
+
20
+ private
21
+
22
+ def validate_tftp
23
+ return super unless host.params['template_url']
24
+ return unless tftp? || tftp6?
25
+ return unless host.operatingsystem
26
+
27
+ pxe_kind = host.operatingsystem.pxe_loader_kind(host)
28
+ return unless pxe_kind && host.provisioning_template(kind: pxe_kind).nil?
29
+
30
+ failure _('No %{kind} template was found for host %{host}. Repository url: %{url}') % { kind: pxe_kind, host: host.name, url: host.params['template_url'] }
31
+ end
22
32
  end
23
33
 
24
34
  included do
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Setting
4
+ class GitTemplates < ::Setting
5
+ def self.default_settings
6
+ [
7
+ set('template_url_validation_timeout', N_('Template URL validation timeout in seconds'), 15, N_('Template URL validation timeout'))
8
+ ]
9
+ end
10
+
11
+ def self.load_defaults
12
+ # Check the table exists
13
+ return unless super
14
+
15
+ transaction do
16
+ default_settings.each { |s| create! s.update(category: 'Setting::GitTemplates') }
17
+ end
18
+
19
+ true
20
+ end
21
+
22
+ def self.humanized_category
23
+ N_('GitTemplates')
24
+ end
25
+ end
26
+ end
@@ -9,7 +9,7 @@ module ForemanGitTemplates
9
9
  end
10
10
 
11
11
  def call
12
- Down.download(repository_url).path
12
+ Down.download(repository_url)
13
13
  rescue Down::ResponseError => e
14
14
  raise RepositoryFetcherError, "Cannot fetch repository from #{repository_url}. Response code: #{e.response.code}"
15
15
  rescue Down::Error => e
@@ -9,6 +9,7 @@ module ForemanGitTemplates
9
9
 
10
10
  def call
11
11
  raise MissingFileError, "The #{file} file is missing" if content.nil?
12
+
12
13
  content
13
14
  end
14
15
 
@@ -30,13 +31,15 @@ module ForemanGitTemplates
30
31
  @content ||= Tar.untar(repository_path) do |tar|
31
32
  return tar.each do |entry|
32
33
  next unless entry.file? && entry.full_name.end_with?(file)
34
+
33
35
  break entry.read.tap do |entry_content|
34
36
  raise EmptyFileError, "The #{file} file is empty" if entry_content.nil?
35
37
  end
36
38
  end
37
39
  end
38
- rescue Errno::ENOENT
39
- raise RepositoryUnreadableError, "Cannot read repository from #{repository_path}"
40
+ rescue Errno::ENOENT, Zlib::GzipFile::Error => e
41
+ Foreman::Logging.exception("GitTemplates: Cannot read repository from #{repository_path}", e)
42
+ raise RepositoryUnreadableError, _('Cannot read repository from %{repository_path}: %{error}') % { repository_path: repository_path, error: e.message }
40
43
  end
41
44
  end
42
45
  end
@@ -6,6 +6,15 @@ module ForemanGitTemplates
6
6
 
7
7
  config.autoload_paths += Dir["#{config.root}/app/lib"]
8
8
  config.autoload_paths += Dir["#{config.root}/app/services"]
9
+ config.autoload_paths += Dir["#{config.root}/app/controllers/concerns"]
10
+
11
+ initializer 'foreman_git_templates.load_default_settings', before: :load_config_initializers do
12
+ require_dependency File.expand_path('../../app/models/setting/git_templates.rb', __dir__) if begin
13
+ Setting.table_exists?
14
+ rescue StandardError
15
+ (false)
16
+ end
17
+ end
9
18
 
10
19
  initializer 'foreman_git_templates.register_plugin', before: :finisher_hook do |_app|
11
20
  Foreman::Plugin.register :foreman_git_templates do
@@ -18,7 +27,10 @@ module ForemanGitTemplates
18
27
  Foreman::Renderer.singleton_class.prepend(ForemanGitTemplates::Renderer)
19
28
  Host::Managed.include(ForemanGitTemplates::Hostext::OperatingSystem)
20
29
  Host::Managed.include(ForemanGitTemplates::HostExtensions)
30
+ HostParameter.include(ForemanGitTemplates::HostParameterExtensions)
21
31
  Nic::Managed.include(ForemanGitTemplates::Orchestration::TFTP)
32
+ UnattendedController.include(ForemanGitTemplates::UnattendedControllerExtensions)
33
+ ::ApplicationHelper.include(ForemanGitTemplates::ApplicationHelper)
22
34
  rescue StandardError => e
23
35
  Rails.logger.warn "ForemanGitTemplates: skipping engine hook (#{e})"
24
36
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ForemanGitTemplates
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.5'
5
5
  end
@@ -2,46 +2,19 @@
2
2
 
3
3
  require 'rake/testtask'
4
4
 
5
- # Tasks
6
- namespace :foreman_git_templates do
7
- namespace :example do
8
- desc 'Example Task'
9
- task task: :environment do
10
- # Task goes here
11
- end
12
- end
13
- end
14
-
15
5
  # Tests
16
6
  namespace :test do
17
7
  desc 'Test ForemanGitTemplates'
18
8
  Rake::TestTask.new(:foreman_git_templates) do |t|
19
- test_dir = File.join(File.dirname(__FILE__), '../..', 'test')
9
+ test_dir = File.join(ForemanGitTemplates::Engine.root, 'test')
20
10
  t.libs << ['test', test_dir]
21
- t.pattern = "#{test_dir}/**/*_test.rb"
11
+ t.pattern = File.join(test_dir, '**', '*_test.rb')
22
12
  t.verbose = true
23
13
  t.warning = false
24
14
  end
25
15
  end
26
16
 
27
- namespace :foreman_git_templates do
28
- task :rubocop do
29
- begin
30
- require 'rubocop/rake_task'
31
- RuboCop::RakeTask.new(:rubocop_foreman_git_templates) do |task|
32
- task.patterns = ["#{ForemanGitTemplates::Engine.root}/app/**/*.rb",
33
- "#{ForemanGitTemplates::Engine.root}/lib/**/*.rb",
34
- "#{ForemanGitTemplates::Engine.root}/test/**/*.rb"]
35
- end
36
- rescue StandardError
37
- puts 'Rubocop not loaded.'
38
- end
39
-
40
- Rake::Task['rubocop_foreman_git_templates'].invoke
41
- end
42
- end
43
-
44
17
  Rake::Task[:test].enhance ['test:foreman_git_templates']
45
18
 
46
19
  load 'tasks/jenkins.rake'
47
- Rake::Task['jenkins:unit'].enhance ['test:foreman_git_templates', 'foreman_git_templates:rubocop'] if Rake::Task.task_defined?(:'jenkins:unit')
20
+ Rake::Task['jenkins:unit'].enhance ['test:foreman_git_templates'] if Rake::Task.task_defined?(:'jenkins:unit')
@@ -3,7 +3,7 @@
3
3
  require 'test_plugin_helper'
4
4
 
5
5
  class HostsControllerTest < ActionController::TestCase
6
- let(:host) { FactoryBot.create(:host, :managed, :with_template_url) }
6
+ let(:host) { FactoryBot.create(:host, :managed, :with_template_url, build: true) }
7
7
 
8
8
  describe '#templates' do
9
9
  it 'returns only templates that are defined in the archive' do
@@ -3,9 +3,9 @@
3
3
  require 'test_plugin_helper'
4
4
 
5
5
  class UnattendedControllerTest < ActionController::TestCase
6
- let(:os) { FactoryBot.create(:operatingsystem, :with_archs, :with_ptables, type: 'Redhat') }
6
+ let(:os) { FactoryBot.create(:operatingsystem, :with_associations, type: 'Redhat') }
7
7
  let(:host) do
8
- FactoryBot.create(:host, :managed, :with_template_url, operatingsystem: os, ptable: os.ptables.first)
8
+ FactoryBot.create(:host, :managed, :with_template_url, build: true, operatingsystem: os, ptable: os.ptables.first)
9
9
  end
10
10
 
11
11
  test 'should render template from repository' do
@@ -68,4 +68,53 @@ class UnattendedControllerTest < ActionController::TestCase
68
68
  assert_equal 'foo bar', response.body.strip
69
69
  end
70
70
  end
71
+
72
+ describe 'iPXE templates' do
73
+ let(:host) do
74
+ FactoryBot.create(:host, :managed, :with_template_url, build: false, operatingsystem: os, ptable: os.ptables.first)
75
+ end
76
+
77
+ context 'host not in build mode' do
78
+ test 'should render iPXE local boot template from repository' do
79
+ assert_not_nil host.params['template_url']
80
+
81
+ Dir.mktmpdir do |dir|
82
+ kind = 'iPXE'
83
+
84
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
85
+ tar.add_file_simple("templates/#{kind}/default_local_boot.erb", 644, 5) { |io| io.write('local') }
86
+ tar.add_file_simple("templates/#{kind}/template.erb", 644, host.name.length) { |io| io.write(host.name) }
87
+ end
88
+
89
+ get :host_template, params: { kind: kind, hostname: host.name }, session: set_session_user
90
+ assert_response :success
91
+ assert_equal 'local', response.body.strip
92
+ end
93
+ end
94
+ end
95
+
96
+ context 'host in build mode' do
97
+ setup do
98
+ host.update!(build: true)
99
+ end
100
+
101
+ test 'should render iPXE template from repository' do
102
+ host.reload
103
+ assert_not_nil host.params['template_url']
104
+
105
+ Dir.mktmpdir do |dir|
106
+ kind = 'iPXE'
107
+
108
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
109
+ tar.add_file_simple("templates/#{kind}/default_local_boot.erb", 644, 5) { |io| io.write('local') }
110
+ tar.add_file_simple("templates/#{kind}/template.erb", 644, host.name.length) { |io| io.write(host.name) }
111
+ end
112
+
113
+ get :host_template, params: { kind: kind, hostname: host.name }, session: set_session_user
114
+ assert_response :success
115
+ assert_equal host.name, response.body.strip
116
+ end
117
+ end
118
+ end
119
+ end
71
120
  end
@@ -4,7 +4,9 @@ FactoryBot.modify do
4
4
  factory :host do
5
5
  trait :with_template_url do
6
6
  after(:create) do |host, _evaluator|
7
- FactoryBot.create(:host_parameter, host: host, name: 'template_url', value: 'http://www.api.com/repository.tar.gz')
7
+ url = 'http://www.api.com/repository.tar.gz'
8
+ WebMock::API.stub_request(:head, url).to_return(status: 200)
9
+ FactoryBot.create(:host_parameter, host: host, name: 'template_url', value: url)
8
10
  end
9
11
  end
10
12
  end
@@ -4,7 +4,33 @@ require 'test_plugin_helper'
4
4
 
5
5
  module Hostext
6
6
  class OperatingSystemTest < ActiveSupport::TestCase
7
- let(:host) { FactoryBot.create(:host, :managed, :with_template_url) }
7
+ let(:host) { FactoryBot.create(:host, :managed, :with_template_url, build: true) }
8
+
9
+ describe '#provisioning_template' do
10
+ it 'finds all PXELinux template kinds' do
11
+ Dir.mktmpdir do |dir|
12
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
13
+ tar.add_file_simple('templates/PXELinux/template.erb', 644, host.name.length) { |io| io.write(host.name) }
14
+ end
15
+
16
+ actual = host.provisioning_template(kind: 'PXELinux')
17
+ assert_equal 'PXELinux', actual.name
18
+ assert_equal host.name, actual.template
19
+ end
20
+ end
21
+
22
+ it 'finds all PXELinux template kinds by symbol' do
23
+ Dir.mktmpdir do |dir|
24
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
25
+ tar.add_file_simple('templates/PXELinux/template.erb', 644, host.name.length) { |io| io.write(host.name) }
26
+ end
27
+
28
+ actual = host.provisioning_template(kind: :PXELinux)
29
+ assert_equal 'PXELinux', actual.name
30
+ assert_equal host.name, actual.template
31
+ end
32
+ end
33
+ end
8
34
 
9
35
  test 'available_template_kinds finds only templates that are defined in the repository' do
10
36
  Dir.mktmpdir do |dir|
@@ -15,10 +15,19 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase
15
15
  Dir.mktmpdir do |dir|
16
16
  stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
17
17
  tar.add_file_simple("templates/#{kind}/template.erb", 644, template_content.length) { |io| io.write(template_content) }
18
- tar.add_file_simple("templates/#{kind}/default_local_boot.erb", 644, default_local_boot_template_content.length) { |io| io.write(default_local_boot_template_content) }
19
18
  end
20
19
 
21
- assert_equal template_content, host.generate_pxe_template('PXELinux')
20
+ assert_equal template_content, host.generate_pxe_template(kind)
21
+ end
22
+ end
23
+
24
+ context 'when corresponding default local boot template file is missing in the repo' do
25
+ it 'does not generate a pxe template' do
26
+ Dir.mktmpdir do |dir|
27
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz"
28
+
29
+ assert_nil host.generate_pxe_template(kind)
30
+ end
22
31
  end
23
32
  end
24
33
  end
@@ -29,41 +38,67 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase
29
38
  it 'renders default local boot template' do
30
39
  Dir.mktmpdir do |dir|
31
40
  stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
32
- tar.add_file_simple("templates/#{kind}/template.erb", 644, template_content.length) { |io| io.write(template_content) }
33
41
  tar.add_file_simple("templates/#{kind}/default_local_boot.erb", 644, default_local_boot_template_content.length) { |io| io.write(default_local_boot_template_content) }
34
42
  end
35
43
 
36
- assert_equal default_local_boot_template_content, host.generate_pxe_template('PXELinux')
44
+ assert_equal default_local_boot_template_content, host.generate_pxe_template(kind)
37
45
  end
38
46
  end
39
47
 
40
- it 'does not generate a pxe template if the corresponding template file is missing in the repo' do
48
+ context 'when corresponding default local boot template file is missing in the repo' do
49
+ it 'does not generate a pxe template' do
50
+ Dir.mktmpdir do |dir|
51
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz"
52
+
53
+ assert_nil host.generate_pxe_template(kind)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#validate_tftp' do
61
+ let(:host) { FactoryBot.create(:host, :with_tftp_orchestration, :with_template_url, build: false, pxe_loader: 'PXELinux BIOS') }
62
+ let(:kind) { 'PXELinux' }
63
+ let(:template_content) { 'main template content' }
64
+ let(:default_local_boot_template_content) { 'default local boot template content' }
65
+
66
+ context 'host is in build mode' do
67
+ setup do
68
+ host.primary_interface.expects(:valid?).returns(true) if Gem::Version.new(SETTINGS[:version].notag) >= Gem::Version.new('2.0')
69
+ host.update(build: true)
70
+ end
71
+
72
+ it 'validates that the host is ready for tftp' do
41
73
  Dir.mktmpdir do |dir|
42
74
  stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
43
75
  tar.add_file_simple("templates/#{kind}/template.erb", 644, template_content.length) { |io| io.write(template_content) }
44
76
  tar.add_file_simple("templates/#{kind}/default_local_boot.erb", 644, default_local_boot_template_content.length) { |io| io.write(default_local_boot_template_content) }
45
77
  end
46
78
 
47
- assert_nil host.generate_pxe_template('PXEGrub2')
79
+ host.provision_interface.send(:validate_tftp)
80
+ assert_empty host.errors.messages
48
81
  end
49
82
  end
83
+ end
50
84
 
51
- it 'raises an exception when generating a pxe template if the corresponding default local boot template file is missing in the repo' do
85
+ context 'host is not build mode' do
86
+ it 'validates that the host is ready for tftp' do
52
87
  Dir.mktmpdir do |dir|
53
88
  stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
54
89
  tar.add_file_simple("templates/#{kind}/template.erb", 644, template_content.length) { |io| io.write(template_content) }
90
+ tar.add_file_simple("templates/#{kind}/default_local_boot.erb", 644, default_local_boot_template_content.length) { |io| io.write(default_local_boot_template_content) }
55
91
  end
56
92
 
57
- assert_raises ForemanGitTemplates::RepositoryReader::MissingFileError do
58
- host.generate_pxe_template('PXELinux')
59
- end
93
+ host.provision_interface.send(:validate_tftp)
94
+ assert_empty host.errors.messages
60
95
  end
61
96
  end
62
97
  end
63
98
  end
64
99
 
65
100
  describe '#setTFTP' do
66
- let(:host) { FactoryBot.create(:host, :with_tftp_orchestration, :with_template_url) }
101
+ let(:host) { FactoryBot.create(:host, :with_tftp_orchestration, :with_template_url, pxe_loader: 'PXELinux BIOS') }
67
102
 
68
103
  let(:kind) { 'PXELinux' }
69
104
  let(:template_content) { 'main template content' }
@@ -71,6 +106,7 @@ class TFTPOrchestrationTest < ActiveSupport::TestCase
71
106
 
72
107
  context 'host is in build mode' do
73
108
  setup do
109
+ host.primary_interface.expects(:valid?).returns(true) if Gem::Version.new(SETTINGS[:version].notag) >= Gem::Version.new('2.0')
74
110
  host.update(build: true)
75
111
  end
76
112
 
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ module ForemanGitTemplates
6
+ class HostParameterTest < ActiveSupport::TestCase
7
+ describe 'template_url validation' do
8
+ let(:template_url) { 'http://api.com/repo.tar.gz' }
9
+ let(:host) { FactoryBot.create(:host) }
10
+ let(:host_parameter) { FactoryBot.build(:host_parameter, name: 'template_url', value: template_url, host: host) }
11
+
12
+ context 'when template_url returns 200' do
13
+ it 'is valid' do
14
+ stub_request(:head, template_url).to_return(status: 200)
15
+
16
+ assert host_parameter.valid?
17
+ end
18
+ end
19
+
20
+ context 'when template_url returns 401' do
21
+ it 'is invlid' do
22
+ stub_request(:head, template_url).to_return(status: 401)
23
+
24
+ assert_equal false, host_parameter.valid?
25
+ assert_not_empty host_parameter.errors[:value]
26
+ end
27
+ end
28
+
29
+ context 'when template_url returns 404' do
30
+ it 'is invlid' do
31
+ stub_request(:head, template_url).to_return(status: 404)
32
+
33
+ assert_equal false, host_parameter.valid?
34
+ assert_not_empty host_parameter.errors[:value]
35
+ end
36
+ end
37
+
38
+ context 'when template_url returns 500' do
39
+ it 'is invlid' do
40
+ stub_request(:head, template_url).to_return(status: 500)
41
+
42
+ assert_equal false, host_parameter.valid?
43
+ assert_not_empty host_parameter.errors[:value]
44
+ end
45
+ end
46
+
47
+ context 'when template_url is not a valid URL' do
48
+ let(:template_url) { 'not URL value' }
49
+
50
+ it 'is invlid' do
51
+ assert_equal false, host_parameter.valid?
52
+ assert_not_empty host_parameter.errors[:value]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -6,11 +6,12 @@ module ForemanGitTemplates
6
6
  class HostTest < ActiveSupport::TestCase
7
7
  describe '#repository_path' do
8
8
  context 'with template_url parameter' do
9
- let(:repo_path) { '/tmp/repo.tar.gz' }
9
+ let(:repo_tempfile) { Tempfile.new }
10
+ let(:repo_path) { repo_tempfile.path }
10
11
  let(:host) { FactoryBot.create(:host, :managed, :with_template_url) }
11
12
 
12
13
  it 'returns path to the repository' do
13
- ForemanGitTemplates::RepositoryFetcher.expects(:call).once.with(host.params['template_url']).returns(repo_path)
14
+ ForemanGitTemplates::RepositoryFetcher.expects(:call).once.with(host.params['template_url']).returns(repo_tempfile)
14
15
  assert_equal repo_path, host.repository_path
15
16
  end
16
17
  end
@@ -24,5 +25,93 @@ module ForemanGitTemplates
24
25
  end
25
26
  end
26
27
  end
28
+
29
+ describe '#update' do
30
+ let(:os) { FactoryBot.create(:operatingsystem, :with_associations, type: 'Redhat') }
31
+
32
+ setup do
33
+ host.expects(:skip_orchestration_for_testing?).at_least_once.returns(false)
34
+ end
35
+
36
+ context 'with valid template_url' do
37
+ setup do
38
+ ProxyAPI::TFTP.any_instance.expects(:set).returns(true)
39
+
40
+ if Gem::Version.new(SETTINGS[:version].notag) >= Gem::Version.new('2.0')
41
+ ProxyAPI::TFTP.any_instance.expects(:bootServer).returns('127.0.0.1')
42
+ ProxyAPI::DHCP.any_instance.expects(:set).returns(true)
43
+ ProxyAPI::DHCP.any_instance.expects(:record).with(host.subnet.network, host.dhcp_records.first.mac).returns(host.dhcp_records.first)
44
+ ProxyAPI::DHCP.any_instance.expects(:records_by_ip).with(host.subnet.network, host.provision_interface.ip).returns([host.dhcp_records.first])
45
+ ProxyAPI::DHCP.any_instance.expects(:delete).returns(true)
46
+ end
47
+ end
48
+
49
+ context 'when host is in build mode' do
50
+ let(:host) { FactoryBot.create(:host, :with_tftp_orchestration, :with_template_url, operatingsystem: os, build: true) }
51
+
52
+ it 'updates the host' do
53
+ ProxyAPI::TFTP.any_instance.expects(:fetch_boot_file).twice.returns(true)
54
+
55
+ Dir.mktmpdir do |dir|
56
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
57
+ tar.add_file_simple('templates/PXEGrub2/template.erb', 644, host.name.length) { |io| io.write(host.name) }
58
+ end
59
+
60
+ assert host.update(name: 'newname')
61
+ assert_empty host.errors.messages
62
+ end
63
+ end
64
+ end
65
+
66
+ context 'when host is not in build mode' do
67
+ let(:host) { FactoryBot.create(:host, :with_tftp_orchestration, :with_template_url, operatingsystem: os, build: false) }
68
+
69
+ it 'updates the host' do
70
+ Dir.mktmpdir do |dir|
71
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz" do |tar|
72
+ tar.add_file_simple('templates/PXEGrub2/default_local_boot.erb', 644, host.name.length) { |io| io.write(host.name) }
73
+ end
74
+
75
+ assert host.update(name: 'newname')
76
+ assert_empty host.errors.messages
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'with invalid template_url' do
83
+ setup do
84
+ stub_request(:get, host.params['template_url']).to_return(status: 404)
85
+
86
+ if Gem::Version.new(SETTINGS[:version].notag) >= Gem::Version.new('2.0')
87
+ ProxyAPI::TFTP.any_instance.expects(:bootServer).returns('127.0.0.1')
88
+ ProxyAPI::DHCP.any_instance.expects(:record).with(host.subnet.network, host.dhcp_records.first.mac).returns(host.dhcp_records.first)
89
+ ProxyAPI::DHCP.any_instance.expects(:records_by_ip).with(host.subnet.network, host.provision_interface.ip).returns([host.dhcp_records.first])
90
+ end
91
+ end
92
+
93
+ let(:expected_errors) { ["No PXEGrub2 template was found for host #{host.name}. Repository url: #{host.params['template_url']}"] }
94
+
95
+ context 'when host is in build mode' do
96
+ let(:host) { FactoryBot.create(:host, :with_tftp_orchestration, :with_template_url, operatingsystem: os, build: true) }
97
+
98
+ it 'does not update the host' do
99
+ assert_equal false, host.update(name: 'newname')
100
+ assert_equal expected_errors, host.errors[:base]
101
+ assert_equal expected_errors, host.errors[:'interfaces.base']
102
+ end
103
+ end
104
+
105
+ context 'when host is not in build mode' do
106
+ let(:host) { FactoryBot.create(:host, :with_tftp_orchestration, :with_template_url, operatingsystem: os, build: false) }
107
+
108
+ it 'does not update the host' do
109
+ assert_equal false, host.update(name: 'newname')
110
+ assert_equal expected_errors, host.errors[:base]
111
+ assert_equal expected_errors, host.errors[:'interfaces.base']
112
+ end
113
+ end
114
+ end
115
+ end
27
116
  end
28
117
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class RendererTest < ActiveSupport::TestCase
6
+ describe '.get_source' do
7
+ subject { Foreman::Renderer.get_source(template: template, host: host, klass: source_klass) }
8
+
9
+ let(:template) { FactoryBot.create(:provisioning_template) }
10
+
11
+ context 'when the host has a template_url defined' do
12
+ let(:host) { FactoryBot.create(:host, :with_template_url) }
13
+
14
+ context 'when the source class is not passed' do
15
+ let(:source_klass) { nil }
16
+
17
+ it 'uses ForemanGitTemplates::Renderer::Source::Repository' do
18
+ Dir.mktmpdir do |dir|
19
+ stub_repository host.params['template_url'], "#{dir}/repo.tar.gz"
20
+
21
+ assert_equal ForemanGitTemplates::Renderer::Source::Repository, subject.class
22
+ end
23
+ end
24
+ end
25
+
26
+ context 'when the source class is implicitly passed' do
27
+ let(:source_klass) { Foreman::Renderer::Source::Database }
28
+
29
+ it { assert_equal Foreman::Renderer::Source::Database, subject.class }
30
+ end
31
+ end
32
+
33
+ context 'when the host has no template_url defined' do
34
+ let(:host) { FactoryBot.create(:host) }
35
+ let(:source_klass) { nil }
36
+
37
+ it { assert_equal Foreman::Renderer::Source::Database, subject.class }
38
+ end
39
+ end
40
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_git_templates
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - dmTECH GmbH
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-04 00:00:00.000000000 Z
11
+ date: 2020-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rdoc
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -42,17 +56,31 @@ dependencies:
42
56
  name: rubocop
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - '='
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
46
74
  - !ruby/object:Gem::Version
47
- version: 0.54.0
75
+ version: '0'
48
76
  type: :development
49
77
  prerelease: false
50
78
  version_requirements: !ruby/object:Gem::Requirement
51
79
  requirements:
52
- - - '='
80
+ - - ">="
53
81
  - !ruby/object:Gem::Version
54
- version: 0.54.0
55
- description:
82
+ version: '0'
83
+ description:
56
84
  email:
57
85
  - opensource@dm.de
58
86
  executables: []
@@ -62,16 +90,20 @@ files:
62
90
  - LICENSE
63
91
  - README.md
64
92
  - Rakefile
93
+ - app/controllers/concerns/foreman_git_templates/unattended_controller_extensions.rb
94
+ - app/helpers/foreman_git_templates/application_helper.rb
65
95
  - app/lib/foreman_git_templates/renderer.rb
66
96
  - app/lib/foreman_git_templates/renderer/source/repository.rb
67
97
  - app/lib/foreman_git_templates/tar.rb
68
98
  - app/models/concerns/foreman_git_templates/host_extensions.rb
99
+ - app/models/concerns/foreman_git_templates/host_parameter_extensions.rb
69
100
  - app/models/concerns/foreman_git_templates/hostext/operating_system.rb
70
101
  - app/models/concerns/foreman_git_templates/orchestration/tftp.rb
71
102
  - app/models/foreman_git_templates/base_repository_template.rb
72
103
  - app/models/foreman_git_templates/default_local_boot_repository_template.rb
73
104
  - app/models/foreman_git_templates/main_repository_template.rb
74
105
  - app/models/foreman_git_templates/snippet_repository_template.rb
106
+ - app/models/setting/git_templates.rb
75
107
  - app/services/foreman_git_templates/repository_fetcher.rb
76
108
  - app/services/foreman_git_templates/repository_reader.rb
77
109
  - config/routes.rb
@@ -84,17 +116,19 @@ files:
84
116
  - test/factories/foreman_git_templates_factories.rb
85
117
  - test/models/concerns/foreman_git_templates/hostext/operating_system_test.rb
86
118
  - test/models/concerns/foreman_git_templates/orchestration/tftp_test.rb
119
+ - test/models/foreman_git_templates/host_parameter_test.rb
87
120
  - test/models/foreman_git_templates/host_test.rb
88
121
  - test/models/foreman_git_templates/snippet_repository_template_test.rb
89
122
  - test/test_plugin_helper.rb
90
123
  - test/unit/foreman/renderer/source/repository_test.rb
124
+ - test/unit/foreman/renderer_test.rb
91
125
  - test/unit/repository_fetcher_test.rb
92
126
  - test/unit/repository_reader_test.rb
93
127
  homepage: https://github.com/dm-drogeriemarkt/foreman_git_templates
94
128
  licenses:
95
129
  - GPL-3.0
96
130
  metadata: {}
97
- post_install_message:
131
+ post_install_message:
98
132
  rdoc_options: []
99
133
  require_paths:
100
134
  - lib
@@ -109,17 +143,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
143
  - !ruby/object:Gem::Version
110
144
  version: '0'
111
145
  requirements: []
112
- rubyforge_project:
113
- rubygems_version: 2.7.3
114
- signing_key:
146
+ rubygems_version: 3.1.2
147
+ signing_key:
115
148
  specification_version: 4
116
149
  summary: Adds support for using templates from Git repositories
117
150
  test_files:
118
151
  - test/unit/repository_fetcher_test.rb
119
152
  - test/unit/foreman/renderer/source/repository_test.rb
153
+ - test/unit/foreman/renderer_test.rb
120
154
  - test/unit/repository_reader_test.rb
121
155
  - test/models/foreman_git_templates/snippet_repository_template_test.rb
122
156
  - test/models/foreman_git_templates/host_test.rb
157
+ - test/models/foreman_git_templates/host_parameter_test.rb
123
158
  - test/models/concerns/foreman_git_templates/hostext/operating_system_test.rb
124
159
  - test/models/concerns/foreman_git_templates/orchestration/tftp_test.rb
125
160
  - test/factories/foreman_git_templates_factories.rb