foreman_git_templates 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +66 -0
  4. data/Rakefile +47 -0
  5. data/app/lib/foreman_git_templates/renderer.rb +20 -0
  6. data/app/lib/foreman_git_templates/renderer/source/repository.rb +27 -0
  7. data/app/lib/foreman_git_templates/tar.rb +26 -0
  8. data/app/models/concerns/foreman_git_templates/host_extensions.rb +11 -0
  9. data/app/models/concerns/foreman_git_templates/hostext/operating_system.rb +34 -0
  10. data/app/models/concerns/foreman_git_templates/orchestration/tftp.rb +29 -0
  11. data/app/models/foreman_git_templates/base_repository_template.rb +15 -0
  12. data/app/models/foreman_git_templates/default_local_boot_repository_template.rb +11 -0
  13. data/app/models/foreman_git_templates/main_repository_template.rb +11 -0
  14. data/app/models/foreman_git_templates/snippet_repository_template.rb +11 -0
  15. data/app/services/foreman_git_templates/repository_fetcher.rb +29 -0
  16. data/app/services/foreman_git_templates/repository_reader.rb +42 -0
  17. data/config/routes.rb +4 -0
  18. data/lib/foreman_git_templates.rb +6 -0
  19. data/lib/foreman_git_templates/engine.rb +26 -0
  20. data/lib/foreman_git_templates/version.rb +5 -0
  21. data/lib/tasks/foreman_git_templates_tasks.rake +47 -0
  22. data/test/controllers/hosts_controller_test.rb +26 -0
  23. data/test/controllers/unattended_controller_test.rb +71 -0
  24. data/test/factories/foreman_git_templates_factories.rb +11 -0
  25. data/test/models/concerns/foreman_git_templates/hostext/operating_system_test.rb +24 -0
  26. data/test/models/concerns/foreman_git_templates/orchestration/tftp_test.rb +114 -0
  27. data/test/models/foreman_git_templates/host_test.rb +28 -0
  28. data/test/models/foreman_git_templates/snippet_repository_template_test.rb +13 -0
  29. data/test/test_plugin_helper.rb +21 -0
  30. data/test/unit/foreman/renderer/source/repository_test.rb +29 -0
  31. data/test/unit/repository_fetcher_test.rb +37 -0
  32. data/test/unit/repository_reader_test.rb +76 -0
  33. metadata +128 -0
@@ -0,0 +1,66 @@
1
+ # ForemanGitTemplates
2
+
3
+ [<img src="https://opensourcelogos.aws.dmtech.cloud/dmTECH_opensource_logo.svg" height="21" width="130">](https://www.dmtech.de/)
4
+
5
+ This is a plugin for Foreman that adds support for using templates from Git repositories.
6
+
7
+ ## Compatibility
8
+
9
+ | Foreman Version | Plugin Version |
10
+ | --------------- | -------------- |
11
+ | >= 1.20 | ~> 1.0 |
12
+
13
+ ## Installation
14
+
15
+ See [Plugins install instructions](https://theforeman.org/plugins/) for how to install Foreman plugins.
16
+
17
+ ## Usage
18
+
19
+ Repositories are fetched as tarball files and must have a specific file structure. The root directory should be named `templates`. Inside the root directory there should be directories for template kinds and for snippets. Only one template for template kind is supported however multiple snippets are supported. Template file should be named `template.erb`. You can also define a default local boot template in the file named `default_local_boot.erb`. Snippet filenames will be downcased and spaces will be replaced by underscores.
20
+
21
+ ```
22
+ .
23
+ └── templates
24
+ ├── PXELinux
25
+ │ ├── template.erb
26
+ │ └── default_local_boot.erb
27
+ ├── provision
28
+ │ └── template.erb
29
+ └── snippets
30
+ ├── snippet_1.erb
31
+ └── snippet_2.erb
32
+ ```
33
+
34
+ In order to use templates from the repository you have to set `template_url` host parameter (specify the HTTP authentication credentials if necessary). To set host parameters navigate to the edit host page and open "Parameters" tab.
35
+
36
+ ![Host Parameters](./doc/images/host_parameters.png)
37
+
38
+ From now the template content for this host will be fetched from the repository. To test this open `/unattended/provision?spoof=<host_ip>`. The template stored in the `provision` directory should be rendered.
39
+
40
+ ### Troubleshooting
41
+
42
+ - `There was an error rendering the provision template: Cannot fetch repository from <template_url>. Response code: 404`
43
+ - check if the URL is correct
44
+ - `There was an error rendering the provision template: Cannot read <template> from repository`
45
+ - check if there is requested template in the repository
46
+
47
+ ## Contributing
48
+
49
+ Fork and send a Pull Request. Thanks!
50
+
51
+ ## Copyright
52
+
53
+ Copyright (c) 2018 dmTECH GmbH, [dmtech.de](https://www.dmtech.de/)
54
+
55
+ This program is free software: you can redistribute it and/or modify
56
+ it under the terms of the GNU General Public License as published by
57
+ the Free Software Foundation, either version 3 of the License, or
58
+ (at your option) any later version.
59
+
60
+ This program is distributed in the hope that it will be useful,
61
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
62
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63
+ GNU General Public License for more details.
64
+
65
+ You should have received a copy of the GNU General Public License
66
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ForemanGitTemplates'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+ task default: :test
37
+
38
+ begin
39
+ require 'rubocop/rake_task'
40
+ RuboCop::RakeTask.new
41
+ rescue => _
42
+ puts 'Rubocop not loaded.'
43
+ end
44
+
45
+ task :default do
46
+ Rake::Task['rubocop'].execute
47
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module Renderer
5
+ def get_source(klass: Foreman::Renderer::Source::Database, template:, **args)
6
+ repository_path = repository_path(args[:host])
7
+ if repository_path
8
+ ForemanGitTemplates::Renderer::Source::Repository.new(template, repository_path)
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def repository_path(host)
17
+ host.try(:repository_path)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module Renderer
5
+ module Source
6
+ class Repository < Foreman::Renderer::Source::Base
7
+ def initialize(template, repository_path)
8
+ @template = template
9
+ @repository_path = repository_path
10
+ end
11
+
12
+ def content
13
+ @content ||= ForemanGitTemplates::RepositoryReader.call(repository_path, template_path)
14
+ end
15
+
16
+ def find_snippet(name)
17
+ SnippetRepositoryTemplate.new(name: name)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :repository_path
23
+ delegate :path, to: :template, prefix: true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+ require 'rubygems/package'
5
+
6
+ module ForemanGitTemplates
7
+ module Tar
8
+ def tar(filepath)
9
+ Zlib::GzipWriter.open(filepath) do |gz|
10
+ Gem::Package::TarWriter.new(gz) do |tar|
11
+ yield tar if block_given?
12
+ end
13
+ end
14
+ end
15
+
16
+ def untar(filepath)
17
+ Zlib::GzipReader.open(filepath) do |gz|
18
+ Gem::Package::TarReader.new(gz) do |tar|
19
+ yield tar if block_given?
20
+ end
21
+ end
22
+ end
23
+
24
+ module_function :tar, :untar
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module HostExtensions
5
+ def repository_path
6
+ return unless host_params['template_url']
7
+
8
+ @repository_path ||= RepositoryFetcher.call(host_params['template_url'])
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module Hostext
5
+ module OperatingSystem
6
+ extend ActiveSupport::Concern
7
+
8
+ module Overrides
9
+ def provisioning_template(opts = {})
10
+ return super unless repository_path
11
+
12
+ kind = opts[:kind] || 'provision'
13
+ available_template_kinds.find { |template| template.name == kind }
14
+ end
15
+
16
+ def available_template_kinds(provisioning = nil)
17
+ return super unless repository_path
18
+
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)
22
+ end
23
+ rescue RepositoryReader::FileUnreadableError # file is missing or empty
24
+ next
25
+ end.compact
26
+ end
27
+ end
28
+
29
+ included do
30
+ prepend Overrides
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ module Orchestration
5
+ module TFTP
6
+ extend ActiveSupport::Concern
7
+
8
+ module Overrides
9
+ delegate :render_template, to: :host
10
+
11
+ def generate_pxe_template(kind)
12
+ return super unless host.repository_path
13
+
14
+ template_klass = build? ? MainRepositoryTemplate : DefaultLocalBootRepositoryTemplate
15
+ template = template_klass.new(name: kind)
16
+ 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)
20
+ nil
21
+ end
22
+ end
23
+
24
+ included do
25
+ prepend Overrides
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ class BaseRepositoryTemplate < ::ProvisioningTemplate
5
+ self.table_name = 'templates'
6
+
7
+ after_initialize do
8
+ self.template_kind = TemplateKind.find_by(name: name)
9
+ end
10
+
11
+ def path
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ class DefaultLocalBootRepositoryTemplate < BaseRepositoryTemplate
5
+ self.table_name = 'templates'
6
+
7
+ def path
8
+ "templates/#{name}/default_local_boot.erb"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ class MainRepositoryTemplate < BaseRepositoryTemplate
5
+ self.table_name = 'templates'
6
+
7
+ def path
8
+ "templates/#{name}/template.erb"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ class SnippetRepositoryTemplate < BaseRepositoryTemplate
5
+ self.table_name = 'templates'
6
+
7
+ def path
8
+ "templates/snippets/#{name.downcase.tr(' ', '_')}.erb"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'down'
4
+
5
+ module ForemanGitTemplates
6
+ class RepositoryFetcher
7
+ def initialize(repository_url)
8
+ @repository_url = repository_url
9
+ end
10
+
11
+ def call
12
+ Down.download(repository_url).path
13
+ rescue Down::ResponseError => e
14
+ raise RepositoryFetcherError, "Cannot fetch repository from #{repository_url}. Response code: #{e.response.code}"
15
+ rescue Down::Error => e
16
+ raise RepositoryFetcherError, "Cannot fetch repository from #{repository_url}, #{e.message}"
17
+ end
18
+
19
+ def self.call(repository_url)
20
+ new(repository_url).call
21
+ end
22
+
23
+ private
24
+
25
+ class RepositoryFetcherError < StandardError; end
26
+
27
+ attr_reader :repository_url
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ class RepositoryReader
5
+ def initialize(repository_path, file)
6
+ @repository_path = repository_path
7
+ @file = file
8
+ end
9
+
10
+ def call
11
+ raise MissingFileError, "The #{file} file is missing" if content.nil?
12
+ content
13
+ end
14
+
15
+ def self.call(repository_path, file)
16
+ new(repository_path, file).call
17
+ end
18
+
19
+ private
20
+
21
+ class RepositoryReaderError < StandardError; end
22
+ class RepositoryUnreadableError < RepositoryReaderError; end
23
+ class FileUnreadableError < RepositoryReaderError; end
24
+ class MissingFileError < FileUnreadableError; end
25
+ class EmptyFileError < FileUnreadableError; end
26
+
27
+ attr_reader :repository_path, :file
28
+
29
+ def content
30
+ @content ||= Tar.untar(repository_path) do |tar|
31
+ return tar.each do |entry|
32
+ next unless entry.file? && entry.full_name.end_with?(file)
33
+ break entry.read.tap do |entry_content|
34
+ raise EmptyFileError, "The #{file} file is empty" if entry_content.nil?
35
+ end
36
+ end
37
+ end
38
+ rescue Errno::ENOENT
39
+ raise RepositoryUnreadableError, "Cannot read repository from #{repository_path}"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'foreman_git_templates/engine'
4
+
5
+ module ForemanGitTemplates
6
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ class Engine < ::Rails::Engine
5
+ engine_name 'foreman_git_templates'
6
+
7
+ config.autoload_paths += Dir["#{config.root}/app/lib"]
8
+ config.autoload_paths += Dir["#{config.root}/app/services"]
9
+
10
+ initializer 'foreman_git_templates.register_plugin', before: :finisher_hook do |_app|
11
+ Foreman::Plugin.register :foreman_git_templates do
12
+ requires_foreman '>= 1.20'
13
+ end
14
+ end
15
+
16
+ # Include concerns in this config.to_prepare block
17
+ config.to_prepare do
18
+ Foreman::Renderer.singleton_class.prepend(ForemanGitTemplates::Renderer)
19
+ Host::Managed.include(ForemanGitTemplates::Hostext::OperatingSystem)
20
+ Host::Managed.include(ForemanGitTemplates::HostExtensions)
21
+ Nic::Managed.include(ForemanGitTemplates::Orchestration::TFTP)
22
+ rescue StandardError => e
23
+ Rails.logger.warn "ForemanGitTemplates: skipping engine hook (#{e})"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ForemanGitTemplates
4
+ VERSION = '1.0.0'
5
+ end