foreman_git_templates 1.0.0 → 1.0.5

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 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