foreman_git_templates 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +66 -0
- data/Rakefile +47 -0
- data/app/lib/foreman_git_templates/renderer.rb +20 -0
- data/app/lib/foreman_git_templates/renderer/source/repository.rb +27 -0
- data/app/lib/foreman_git_templates/tar.rb +26 -0
- data/app/models/concerns/foreman_git_templates/host_extensions.rb +11 -0
- data/app/models/concerns/foreman_git_templates/hostext/operating_system.rb +34 -0
- data/app/models/concerns/foreman_git_templates/orchestration/tftp.rb +29 -0
- data/app/models/foreman_git_templates/base_repository_template.rb +15 -0
- data/app/models/foreman_git_templates/default_local_boot_repository_template.rb +11 -0
- data/app/models/foreman_git_templates/main_repository_template.rb +11 -0
- data/app/models/foreman_git_templates/snippet_repository_template.rb +11 -0
- data/app/services/foreman_git_templates/repository_fetcher.rb +29 -0
- data/app/services/foreman_git_templates/repository_reader.rb +42 -0
- data/config/routes.rb +4 -0
- data/lib/foreman_git_templates.rb +6 -0
- data/lib/foreman_git_templates/engine.rb +26 -0
- data/lib/foreman_git_templates/version.rb +5 -0
- data/lib/tasks/foreman_git_templates_tasks.rake +47 -0
- data/test/controllers/hosts_controller_test.rb +26 -0
- data/test/controllers/unattended_controller_test.rb +71 -0
- data/test/factories/foreman_git_templates_factories.rb +11 -0
- data/test/models/concerns/foreman_git_templates/hostext/operating_system_test.rb +24 -0
- data/test/models/concerns/foreman_git_templates/orchestration/tftp_test.rb +114 -0
- data/test/models/foreman_git_templates/host_test.rb +28 -0
- data/test/models/foreman_git_templates/snippet_repository_template_test.rb +13 -0
- data/test/test_plugin_helper.rb +21 -0
- data/test/unit/foreman/renderer/source/repository_test.rb +29 -0
- data/test/unit/repository_fetcher_test.rb +37 -0
- data/test/unit/repository_reader_test.rb +76 -0
- metadata +128 -0
data/README.md
ADDED
@@ -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/>.
|
data/Rakefile
ADDED
@@ -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,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,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
|
data/config/routes.rb
ADDED
@@ -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
|