Skip to content
This repository has been archived by the owner on Feb 22, 2024. It is now read-only.

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
timogoebel committed Jan 24, 2017
0 parents commit abc52d8
Show file tree
Hide file tree
Showing 15 changed files with 1,007 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
@@ -0,0 +1,12 @@
.bundle/
log/*.log
pkg/
test/dummy/db/*.sqlite3
test/dummy/log/*.log
test/dummy/tmp/
test/dummy/.sass-cache
locale/*.mo
locale/*/*.edit.po
locale/*/*.po.time_stamp
locale/*/*.pox
Gemfile.lock
30 changes: 30 additions & 0 deletions .rubocop.yml
@@ -0,0 +1,30 @@
Rails:
Enabled: true

# Don't enforce documentation
Style/Documentation:
Enabled: false

Rails/ActionFilter:
EnforcedStyle: action

Metrics/MethodLength:
Max: 20

Style/Next:
Enabled: false

# Support both ruby19 and hash_rockets
Style/HashSyntax:
Enabled: false

Metrics/ClassLength:
Exclude:
- 'test/**/*'

Performance/FixedSize:
Exclude:
- 'test/**/*'

Metrics/LineLength:
Max: 100
3 changes: 3 additions & 0 deletions Gemfile
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gemspec
619 changes: 619 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions README.md
@@ -0,0 +1,58 @@
# ForemanUserdata

This plug-in adds a user-data endpoint to [The Foreman](https://theforeman.org/) for usage with cloud-init.

## Compatibility

| Foreman Version | Plugin Version |
| --------------- | -------------- |
| >= 1.12 | any |

## Installation

See [Plugins install instructions](https://theforeman.org/plugins/)
for how to install Foreman plugins.
You need to install the package `tfm-rubygem-foreman_userdata`.

## Client setup

On RHEL7 using cloud-init from EPEL:

```
yum install cloud-init -y
cat << EOF > /etc/cloud/cloud.cfg.d/10_foreman.cfg
datasource_list: [NoCloud]
datasource:
NoCloud:
seedfrom: http://foreman.example.com/userdata/
EOF
```

## Client debug

```
# Purge all cloud-init data
rm -rf /var/lib/cloud/*
# Run in foreground with debug mode enabled
/usr/bin/cloud-init -d init
```

## Copyright

Copyright (c) 2016 Timo Goebel

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

47 changes: 47 additions & 0 deletions Rakefile
@@ -0,0 +1,47 @@
#!/usr/bin/env rake
begin
require 'bundler/setup'
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
begin
require 'rdoc/task'
rescue LoadError
require 'rdoc/rdoc'
require 'rake/rdoctask'
RDoc::Task = Rake::RDocTask
end

RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'ForemanUserdata'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
end

APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)

Bundler::GemHelper.install_tasks

require 'rake/testtask'

Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = false
end

task default: :test

begin
require 'rubocop/rake_task'
RuboCop::RakeTask.new
rescue => _
puts 'Rubocop not loaded.'
end

task :default do
Rake::Task['rubocop'].execute
end
101 changes: 101 additions & 0 deletions app/controllers/userdata_controller.rb
@@ -0,0 +1,101 @@
class UserdataController < ApplicationController
# Skip default filters for user-data
FILTERS = [
:require_login,
:session_expiry,
:update_activity_time,
:set_taxonomy,
:authorize,
:verify_authenticity_token
].freeze

FILTERS.each do |f|
skip_before_action f
end

before_action :skip_secure_headers
before_action :find_host

def userdata
template = render_userdata_template
render :plain => template if template
end

def metadata
render :plain => ''
end

private

def render_userdata_template
template = @host.provisioning_template(:kind => 'user_data')
unless template
render_error(
:message => 'Unable to find user-data template for host %{host} running %{os}',
:status => :not_found,
:host => @host.name,
:os => @host.operatingsystem
)
return
end
safe_render(template)
end

def safe_render(template)
@host.render_template(template)
rescue StandardError => error
Foreman::Logging.exception("Error rendering the #{template.name} template", error)
render_error(
:message => 'There was an error rendering the %{name} template: %{error}',
:name => template.name,
:error => error.message,
:status => :internal_server_error
)
return false
end

def skip_secure_headers
SecureHeaders.opt_out_of_all_protection(request)
end

def render_error(options)
message = options.delete(:message)
status = options.delete(:status) || :not_found
logger.error message % options
render :plain => "#{message % options}\n", :status => status
end

def find_host
@host = find_host_by_ip
return true if @host
render_error(
:message => 'Could not find host for request %{request_ip}',
:status => :not_found,
:request_ip => ip_from_request_env
)
false
end

def find_host_by_ip
# try to find host based on our client ip address
ip = ip_from_request_env

# in case we got back multiple ips (see #1619)
ip = ip.split(',').first

# host is readonly because of association so we reload it if we find it
host = Host.joins(:provision_interface).where(:nics => { :ip => ip }).first
host ? Host.find(host.id) : nil
end

def ip_from_request_env
ip = request.env['REMOTE_ADDR']

# check if someone is asking on behalf of another system (load balancer etc)
if request.env['HTTP_X_FORWARDED_FOR'].present? && (ip =~ Regexp.new(Setting[:remote_addr]))
ip = request.env['HTTP_X_FORWARDED_FOR']
end

ip
end
end
4 changes: 4 additions & 0 deletions config/routes.rb
@@ -0,0 +1,4 @@
Rails.application.routes.draw do
get 'userdata/meta-data', :controller => 'userdata', :action => 'metadata', :format => 'text'
get 'userdata/user-data', :controller => 'userdata', :action => 'userdata', :format => 'text'
end
23 changes: 23 additions & 0 deletions foreman_userdata.gemspec
@@ -0,0 +1,23 @@
require File.expand_path('../lib/foreman_userdata/version', __FILE__)
require 'date'

Gem::Specification.new do |s|
s.name = 'foreman_userdata'
s.version = ForemanUserdata::VERSION
# rubocop:disable Date
s.date = Date.today.to_s
# rubocop:enable Date
s.authors = ['Timo Goebel']
s.email = ['mail@timogoebel.name']
s.homepage = 'http://github.com/theforeman/foreman_userdata'
s.licenses = ['GPL-3']
s.summary = 'This plug-in adds support for serving user-data for cloud-init to The Foreman.'
# also update locale/gemspec.rb
s.description = 'This plug-in adds support for serving user-data for cloud-init to The Foreman.'

s.files = Dir['{app,config,db,lib,locale}/**/*'] + ['LICENSE', 'Rakefile', 'README.md']
s.test_files = Dir['test/**/*']

s.add_development_dependency 'rubocop'
s.add_development_dependency 'rdoc'
end
4 changes: 4 additions & 0 deletions lib/foreman_userdata.rb
@@ -0,0 +1,4 @@
require 'foreman_userdata/engine'

module ForemanUserdata
end
11 changes: 11 additions & 0 deletions lib/foreman_userdata/engine.rb
@@ -0,0 +1,11 @@
module ForemanUserdata
class Engine < ::Rails::Engine
engine_name 'foreman_userdata'

initializer 'foreman_userdata.register_plugin', :before => :finisher_hook do |_app|
Foreman::Plugin.register :foreman_userdata do
requires_foreman '>= 1.12'
end
end
end
end
3 changes: 3 additions & 0 deletions lib/foreman_userdata/version.rb
@@ -0,0 +1,3 @@
module ForemanUserdata
VERSION = '0.0.1'.freeze
end
45 changes: 45 additions & 0 deletions lib/tasks/foreman_userdata_tasks.rake
@@ -0,0 +1,45 @@
# Tasks
namespace :foreman_userdata do
namespace :example do
desc 'Example Task'
task task: :environment do
# Task goes here
end
end
end

# Tests
namespace :test do
desc 'Test ForemanUserdata'
Rake::TestTask.new(:foreman_userdata) do |t|
test_dir = File.join(File.dirname(__FILE__), '../..', 'test')
t.libs << ['test', test_dir]
t.pattern = "#{test_dir}/**/*_test.rb"
t.verbose = true
t.warning = false
end
end

namespace :foreman_userdata do
task :rubocop do
begin
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop_foreman_userdata) do |task|
task.patterns = ["#{ForemanUserdata::Engine.root}/app/**/*.rb",
"#{ForemanUserdata::Engine.root}/lib/**/*.rb",
"#{ForemanUserdata::Engine.root}/test/**/*.rb"]
end
rescue
puts 'Rubocop not loaded.'
end

Rake::Task['rubocop_foreman_userdata'].invoke
end
end

Rake::Task[:test].enhance ['test:foreman_userdata']

load 'tasks/jenkins.rake'
if Rake::Task.task_defined?(:'jenkins:unit')
Rake::Task['jenkins:unit'].enhance ['test:foreman_userdata', 'foreman_userdata:rubocop']
end
29 changes: 29 additions & 0 deletions test/controllers/userdata_controller_test.rb
@@ -0,0 +1,29 @@
require 'test_plugin_helper'

class UserdataControllerTest < ActionController::TestCase
let(:host) { FactoryGirl.create(:host, :managed) }
let(:content) { 'template content' }
let(:template_kind) { TemplateKind.create(:name => 'user_data') }
let(:template) do
FactoryGirl.create(
:provisioning_template,
:template_kind => template_kind,
:template => content
)
end

test 'should get rendered userdata template' do
@request.env['REMOTE_ADDR'] = host.ip
Host::Managed.any_instance.expects(:provisioning_template).returns(template)
get :userdata
assert_response :success
assert_equal content, @response.body
end

test 'should get empty metadata' do
@request.env['REMOTE_ADDR'] = host.ip
get :metadata
assert_response :success
assert_empty @response.body
end
end

0 comments on commit abc52d8

Please sign in to comment.