Skip to content
Snippets Groups Projects
Verified Commit 53ebb8ed authored by Alexander Olofsson's avatar Alexander Olofsson
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #100211 failed
Showing
with 580 additions and 0 deletions
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
---
include:
- project: ITI/ci-pipelines
file: '/foreman-module.yaml'
AllCops:
TargetRubyVersion: 2.6
Style/StringLiterals:
Enabled: true
EnforcedStyle: double_quotes
Style/StringLiteralsInInterpolation:
Enabled: true
EnforcedStyle: double_quotes
Layout/LineLength:
Max: 120
Gemfile 0 → 100644
# frozen_string_literal: true
source "https://rubygems.org"
# Specify your gem's dependencies in foreman_liudesk_cmdb.gemspec
gemspec
gem "rake", "~> 13.0"
gem "minitest", "~> 5.0"
gem "rubocop", "~> 1.21"
This diff is collapsed.
# Foreman Liudesk CMDB
A plugin to push inventory data into the Linköping University CMDB system
## Compatibility
| Foreman Version | Plugin Version |
| --------------- | -------------- |
| >= 3.0 | any |
## Installation
See the [Plugins install instructions, advanced installation from gems](https://theforeman.org/plugins/#2.3AdvancedInstallationfromGems) for information on how to install this plugins.
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ananace/foreman_liudesk_cmdb
The project lives at https://gitlab.liu.se/ITI/foreman_liudesk_cmdb
## License
The gem is available as open source under the terms of the [GPL-3.0 License](https://opensource.org/licenses/GPL-3.0).
Rakefile 0 → 100644
# frozen_string_literal: true
require "bundler/gem_tasks"
require "rake/testtask"
Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/test_*.rb"]
end
require "rubocop/rake_task"
RuboCop::RakeTask.new
task default: %i[test rubocop]
# frozen_string_literal: true
module ForemanLiudeskCMDB
module HostExtensions
extend ActiveSupport::Concern
def self.prepended(base)
base.class_eval do
# TODO
# after_build :ensure_passwordstate_host
# before_provision :remove_passwordstate_host
# has_one :liudesk_cmdb_facet,
# class_name: "ForemanLiudeskCMDB::LiudeskCMDBFacet",
# foreign_key: :host_id,
# inverse_of: :host,
# dependent: :destroy
# scoped_search on: :passwordstate_server_id,
# relation: :passwordstate_facet,
# rename: :passwordstate_server,
# complete_value: true,
# only_explicit: true
after_update :ensure_cmdb_entry
end
end
def liudesk_cmdb_facet!(**attrs)
return liudesk_cmdb_facet if liudesk_cmdb_facet && attrs.empty?
attrs = liudesk_cmdb_facet.attributes.merge(attrs) if liudesk_cmdb_facet
attrs = hostgroup.inherited_facet_attributes(Facets.registered_facets[:liudesk_cmdb_facet]).merge(attrs) \
if hostgroup
if liudesk_cmdb_facet
f = liudesk_cmdb_facet
f.update_attributes attrs
else
f = build_liudesk_cmdb_facet attrs
end
f.save if persisted?
f
end
def cmdb_client?
return unless liudesk_cmdb_facet
liudesk_cmdb_facet.client?
end
def cmdb_client=(client)
raise "Not a CMDB asset" unless liudesk_cmdb_facet
raise ArgumentError, "Not a boolean" unless [FalseClass, TrueClass].include? client.class
# FIXME: Handle changing client <-> server
liudesk_cmdb_facet.asset_type = client ? :client : :server
end
def cmdb_asset_info(create: false)
info = {
hostname: name
}
info[:network_access_role] = "None" if create
info[:operating_system] = os&.title || "N/A"
info[:operating_system_type] = os&.name || "N/A"
info[:operating_system_install_date] = installed_at
if managed?
info[:management_system] = "ITI-Foreman"
info[:management_system_id] = "#{SETTINGS[:fqdn]}/#{id}"
info[:certificate_information] = certname
info[:network_certificate_ca] = "PUPPETCA" if liudesk_cmdb_facet&.client?
info[:foreman_link] = "https://#{SETTINGS[:fqdn]}/hosts/#{fqdn}" unless liudesk_cmdb_facet&.client?
end
info.compact
end
def cmdb_hardware_search
info = {
serial_number: facts["dmi::product::serial_number"],
bios_uuid: facts["dmi::product::uuid"] || facts["uuid"]
}
info.compact
end
def cmdb_hardware_info(create: false)
info = cmdb_hardware_search
info.merge!(
make: facts["dmi::manufacturer"] || "N/A",
model: facts["dmi::product::name"]
)
info[:mac_and_network_access_roles] = [mac: "", networkAccessRole: "None"] if create
# Strip out manufacturer from product name, to avoid double-manufacturer identifiers
info[:model] = info[:model]&.sub(info[:make], "")&.strip if info[:model]&.start_with? info[:make]
info[:model] = "N/A" if info[:model].nil? || info[:model].empty?
info.compact
end
private
def ensure_cmdb_entry
return unless liudesk_cmdb_facet
# TODO: Fully differential sync, a.k.a; no get, only put
# asset = liudesk_cmdb_facet.asset(thin: true)
# asset.blah = blah if blah_changed?
# asset.save! if asset.changed?
# hw = liudesk_cmdb_facet.hardware(thin: true)
# hw.blah = blah if blah_changed?
# hw.save! if hw.changed?
# FIXME: This is ugly, should attempt assign elsewhere - service probably
unless liudesk_cmdb_facet.asset_id
reset_asset_id = true
liudesk_cmdb_facet.asset_id = fqdn
end
asset = nil
begin
asset = liudesk_cmdb_facet.asset
rescue LiudeskCMDB::NotFoundError
liudesk_cmdb_facet.asset_id = nil if reset_asset_id
# 404 is ok here
end
liudesk_cmdb_facet.hardware_id ||= asset.hardware_id if asset
# FIXME: Needs more graceful error handling
hw = liudesk_cmdb_facet.hardware!
hw.update_attributes(**cmdb_hardware_info)
hw.patch! if hw.changed?
asset ||= liudesk_cmdb_facet.asset!
asset.update_attributes(**cmdb_asset_info)
asset.patch! if asset.changed?
liudesk_cmdb_facet.save! if liudesk_cmdb_facet.changed?
true
rescue StandardError => e
Foreman::Logging.exception "Failed to update CMDB entry - #{e.class}: #{e}", e, logger: "foreman_liudesk_cmdb"
nil
end
end
end
# frozen_string_literal: true
module ForemanLiudeskCMDB
class LiudeskCMDBFacet < ApplicationRecord
class Jail < Safemode::Jail
allow :asset_type
end
include Facets::Base
belongs_to :liudesk_cmdb_server,
class_name: "LiudeskCMDBServer",
inverse_of: :liudesk_cmdb_facets
validates_lengths_from_database
validates :asset_type, presence: true
def asset_model_type
if asset_type.to_s.downcase == "server"
:server_v1
elsif asset_type.to_s.downcase == "client"
case host.os&.family
when /^windows/i
:windows_client_v1
else
:linux_client_v1
end
else
asset_type.to_s.downcase.to_sym
end
end
def client?
asset_type.to_s != "server"
end
def asset(thin: false)
return unless asset_id
liudesk_cmdb_server.get_asset(asset_model_type, asset_id, thin: thin)
end
def asset!(thin: false)
return asset(thin: thin) if asset_id
raise "Missing hardware ID" unless hardware_id
new_asset = nil
begin
new_asset = liudesk_cmdb_server.get_asset(asset_model_type, host.name)
rescue LiudeskCMDB::Error
# Asset not found, create a new one
end
new_asset ||= liudesk_cmdb_server.create_asset(
asset_model_type, **host.cmdb_asset_info(create: true).merge(hardware_id: hardware_id)
)
self.asset_id = new_asset.identifier
new_asset
end
def hardware(thin: false)
return unless hardware_id
liudesk_cmdb_server.get_asset(:hardware_v1, hardware_id, thin: thin)
end
def hardware!(thin: false)
return hardware(thin: thin) if hardware_id
if host.cmdb_hardware_search.empty?
hw = []
else
hw = liudesk_cmdb_server.find_asset(:hardware_v1, **host.cmdb_hardware_search)
raise "Found multiple valid hardwares" if hw.count > 1
end
hw = if hw.count == 1
hw.first
else
liudesk_cmdb_server.create_asset(:hardware_v1, **host.cmdb_hardware_info(create: true))
end
self.hardware_id = hw.identifier
hw
end
end
end
# frozen_string_literal: true
module ForemanLiudeskCMDB
class LiudeskCMDBHostgroupFacet < ApplicationRecord
class Jail < Safemode::Jail
allow :asset_type
end
include Facets::Base
belongs_to :liudesk_cmdb_server,
class_name: "LiudeskCMDBServer",
inverse_of: :liudesk_cmdb_hostgroup_facets
validates_lengths_from_database
validates :asset_type, presence: true
end
end
# frozen_string_literal: true
class LiudeskCMDBServer < ApplicationRecord
class Jail < Safemode::Jail
allow :name
end
include Encryptable
include Parameterizable::ByIdName
extend FriendlyId
friendly_id :name
encrypts :apikey
validates_lengths_from_database
audited except: [:apikey]
has_associated_audits
before_destroy EnsureNotUsedBy.new(:hosts)
has_many :liudesk_cmdb_facets,
class_name: "ForemanLiudeskCMDB::LiudeskCMDBFacet",
dependent: :nullify,
inverse_of: :liudesk_cmdb_server
has_many :liudesk_cmdb_hostgroup_facets,
class_name: "ForemanLiudeskCMDB::LiudeskCMDBHostgroupFacet",
dependent: :nullify,
inverse_of: :liudesk_cmdb_server
validates :name, presence: true, uniqueness: true
validates :url, presence: true
validates :apikey, presence: true
scoped_search on: :name, complete_value: true
default_scope -> { order("liudesk_cmdb_servers.name") }
def get_asset(asset_type, asset_id, thin: false)
klass = LiudeskCMDB::Models.const_get(asset_type.to_s.camel_case(capitalized: true).to_sym)
return klass.new connection, asset_id if thin
klass.get connection, asset_id
end
def find_asset(asset_type, **search)
klass = LiudeskCMDB::Models.const_get(asset_type.to_s.camel_case(capitalized: true).to_sym)
klass.search connection, op: :or, **search
end
def create_asset(asset_type, save: true, **data)
klass = LiudeskCMDB::Models.const_get(asset_type.to_s.camel_case(capitalized: true).to_sym)
asset = klass.new connection, **data
asset.create if save
asset
end
private
def connection
@connection ||= LiudeskCMDB::Client.new url, subscription_key: apikey
end
end
<%-
item = %w[hosts discovered_hosts].include?(controller_name) ? @host : @hostgroup
facet = item.try(:passwordstate_facet)
if !(item.persisted? rescue false)
hostgroup = @hostgroup || @host.hostgroup
inherited = facet.nil?
inherited ||= true if hostgroup&.inherited_facet_attributes(Facets.registered_facets[:passwordstate_facet])&.any?
facet ||= item.build_passwordstate_facet(hostgroup.inherited_facet_attributes(Facets.registered_facets[:passwordstate_facet])) if inherited && hostgroup
end
-%>
<%= fields_for item do |f| %>
<%= f.fields_for 'liudesk_cmdb_facet_attributes' do |f| %>
<%-
begin
servers = LiudeskCMDBServer.all.map do |server|
name = "#{server.name} - #{server.url}"
name += " (not available)" unless server.test_connection
[ server.id, name ]
end
rescue StandardError => e
-%>
<!--
Errors occured during rendering;
<%= e.class %>: <%= e %>
<%= e.backtrace.join "\n" %>
-->
<%-
servers = []
end
server_id = facet.try :liudesk_cmdb_server_id rescue nil
cmdb_client = facet.try :asset_type rescue nil
cmdb_client = true if cmdb_client == :client
-%>
<%= select_f f, :liudesk_cmdb_server_id, servers, :first, :last,
{ include_blank: true,
selected: server_id,
# disable_button: _(HostsAndHostgroupsHelper::INHERIT_TEXT),
# disable_button_enabled: inherited,
},
{ disabled: servers.empty?,
help_inline: :indicator,
label: _('CMDB Asset Store'),
label_help_options: { data: { placement: 'top' } }
}
%>
<%= add_label { label: _('Asset Type') }, f, { } %>
<%= radio_button_f f, :asset_type_meta, value: cmdb_client, text: "Client", disabled: servers.empty? %>
<%= radio_button_f f, :asset_type_meta, value: !cmdb_client, text: "Server", disabled: servers.empty? %>
<% end -%>
<% end -%>
# frozen_string_literal: true
class AddLiudeskCmdb < ActiveRecord::Migration[6.1]
def change
create_table :liudesk_cmdb_servers do |t|
t.string :name, null: false, unique: true
t.string :description, limit: 255
t.string :url, null: false, limit: 255
t.string :apikey, null: false, limit: 64
t.timestamps null: false
end
create_table :liudesk_cmdb_facets do |t|
t.references :liudesk_cmdb_server, null: false, foreign_key: true, index: true
t.integer :host_id, null: false, index: true
t.string :asset_type, null: false
t.string :asset_id, null: true
t.string :hardware_id, null: true
# t.timestamp :last_full_sync, null: true
t.timestamps null: false
end
create_table :liudesk_cmdb_hostgroup_facets do |t|
t.references :liudesk_cmdb_server, null: false, foreign_key: true, index: true
t.integer :hostgroup_id, null: false, index: true
t.string :asset_type, null: false
t.timestamps null: false
end
end
end
# frozen_string_literal: true
require_relative "lib/foreman_liudesk_cmdb/version"
Gem::Specification.new do |spec|
spec.name = "foreman_liudesk_cmdb"
spec.version = ForemanLiudeskCMDB::VERSION
spec.authors = ["Alexander Olofsson"]
spec.email = ["alexander.olofsson@liu.se"]
spec.homepage = "https://github.com/ananace/foreman_liudesk_cmdb"
spec.summary = "Integrate Foreman with the Linköping University CMDB"
spec.description = spec.summary
spec.license = "GPL-3.0"
spec.required_ruby_version = ">= 2.6.0"
spec.files = Dir["{app,config,db,lib}/**/*.{rake,rb}"] + %w[LICENSE.txt README.md]
spec.add_dependency "liudesk_cmdb"
end
# frozen_string_literal: true
require_relative "foreman_liudesk_cmdb/version"
require_relative "foreman_liudesk_cmdb/engine"
module ForemanLiudeskCMDB
class Error < StandardError; end
# Your code goes here...
end
# frozen_string_literal: true
module ForemanLiudeskCMDB
class Engine < ::Rails::Engine
engine_name "foreman_liudesk_cmdb"
config.autoload_paths += Dir["#{config.root}/app/models/concerns"]
# config.autoload_paths += Dir["#{config.root}/app/services"]
initializer "foreman_liudesk_cmdb.load_app_instance_data" do |app|
ForemanLiudeskCMDB::Engine.paths["db/migrate"].existent.each do |path|
app.config.paths["db/migrate"] << path
end
end
initializer "foreman_liudesk_cmdb.register_plugin", before: :finisher_hook do |_app|
Foreman::Plugin.register :foreman_liudesk_cmdb do
requires_foreman ">= 3.0"
register_facet ForemanLiudeskCMDB::LiudeskCMDBFacet, :liudesk_cmdb_facet do
configure_host do
set_dependent_action :destroy
extend_model ForemanLiudeskCMDB::HostExtensions
# add_tabs liudesk_cmdb_facet: "foreman_liudesk/liudesk_cmdb_facet"
end
configure_hostgroup ForemanLiudeskCMDB::LiudeskCMDBHostgroupFacet do
set_dependent_action :destroy
end
end
end
end
config.to_prepare do
# XXX
rescue StandardError => e
Rails.logger.fatal "foreman_liudesk_cmdb: skipping engine hook (#{e})"
end
end
end
# frozen_string_literal: true
module ForemanLiudeskCMDB
VERSION = "0.1.0"
end
# frozen_string_literal: true
require "test_helper"
class TestForemanLiudeskCMDB < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::ForemanLiudeskCMDB::VERSION
end
def test_it_does_something_useful
assert false
end
end
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "foreman_liudesk_cmdb"
require "minitest/autorun"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment