foreman_teamdynamix 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +99 -0
- data/Rakefile +47 -0
- data/app/controllers/concerns/foreman_teamdynamix/hosts_controller_extensions.rb +23 -0
- data/app/helpers/concerns/foreman_teamdynamix/hosts_helper_extensions.rb +57 -0
- data/app/models/concerns/foreman_teamdynamix/host_extensions.rb +32 -0
- data/app/overrides/add_tab.rb +7 -0
- data/app/overrides/add_tab_link.rb +10 -0
- data/app/services/teamdynamix_api.rb +150 -0
- data/app/views/foreman_teamdynamix/hosts/_teamdynamix.html.erb +15 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20171228033228_add_teamdynamix_asset_id_to_hosts.foreman_teamdynamix.rb +5 -0
- data/lib/foreman_teamdynamix/engine.rb +50 -0
- data/lib/foreman_teamdynamix/version.rb +3 -0
- data/lib/foreman_teamdynamix.rb +4 -0
- data/lib/tasks/foreman_teamdynamix_tasks.rake +40 -0
- data/lib/tasks/sync_hosts_with_teamdynamix.rake +96 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_teamdynamix.po +18 -0
- data/locale/foreman_teamdynamix.pot +18 -0
- data/locale/gemspec.rb +2 -0
- data/test/fake_teamdynamix_api.rb +17 -0
- data/test/functional/concerns/hosts_controller_extensions_test.rb +31 -0
- data/test/helpers/concerns/foreman_teamdynamix/hosts_helper_extensions_test.rb +63 -0
- data/test/models/host_extensions_test.rb +37 -0
- data/test/sample_asset.json +193 -0
- data/test/services/teamdynamix_api_test.rb +165 -0
- data/test/test_plugin_helper.rb +38 -0
- data/test/unit/foreman_teamdynamix_test.rb +11 -0
- metadata +124 -0
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# foreman_teamdynamix
|
2
|
+
A Foreman Plugin for TeamDynamix. It manages a host's life cycle as a corresponding Asset in TeamDynamix.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
|
6
|
+
To install foreman_teamdynamix require it in your gem file by adding the line.
|
7
|
+
```
|
8
|
+
gem 'foreman_teamdynamix'
|
9
|
+
```
|
10
|
+
Then update foreman to include the gem with the command.
|
11
|
+
```
|
12
|
+
bundle update foreman_teamdynamix
|
13
|
+
```
|
14
|
+
|
15
|
+
## Configuration
|
16
|
+
To setup the configuration file create a new file named 'foreman_teamdynamix.yaml' at the location /etc/foreman/plugins/
|
17
|
+
|
18
|
+
If there is no configuration file then the tab should not appear on the detailed hosts screen, but if there is one and it is empty then it will appear without any fields.
|
19
|
+
|
20
|
+
Example Configuration
|
21
|
+
|
22
|
+
```
|
23
|
+
---
|
24
|
+
:teamdynamix:
|
25
|
+
:api:
|
26
|
+
:url: 'https://miamioh.teamdynamix.com/SBTDWebApi/api'
|
27
|
+
:appId: 741
|
28
|
+
:username: 'xxxxxx'
|
29
|
+
:password: 'xxxxxx'
|
30
|
+
:create:
|
31
|
+
:StatusID: 641
|
32
|
+
:OwningCustomerName: foreman_teamdynamix_plugin_test
|
33
|
+
:Attributes:
|
34
|
+
- name: mu.ci.Lifecycle Status
|
35
|
+
id: 11634
|
36
|
+
value: 26190
|
37
|
+
- name: mu.ci.Description
|
38
|
+
id: 11632
|
39
|
+
value: "created by ForemanTeamdynamix plugin, owner is #{host.owner_id}"
|
40
|
+
- name: Ticket Routing Details
|
41
|
+
id: 11636
|
42
|
+
value: "Asset for host running on OS #{host.operatingsystem_id}"
|
43
|
+
:delete:
|
44
|
+
:StatusId: 642
|
45
|
+
:search:
|
46
|
+
AppID: 741
|
47
|
+
StatusName: In Use
|
48
|
+
RequestingCustomerID: 00000000-0000-0000-0000-000000000000
|
49
|
+
OwningDepartmentID: 15798
|
50
|
+
:fields:
|
51
|
+
Asset ID: ID
|
52
|
+
Owner: OwningCustomerName
|
53
|
+
Parent Asset: ParentID
|
54
|
+
mu.ci.Description: Attributes.mu.ci.Description
|
55
|
+
Ticket Routing Details: Attributes.Ticket Routing Details
|
56
|
+
mu.ci.Lifecycle Status: Attributes.mu.ci.Lifecycle Status
|
57
|
+
```
|
58
|
+
[:api][:create] or [:delete]
|
59
|
+
* All attributes are passed to the TeamDynamix API as is, while creating or deleting a TeamDynamix Asset.
|
60
|
+
* An asset gets created or deleted with the Foreman Host create or delete life cycle event.
|
61
|
+
|
62
|
+
[:api][:create][:Attributes]
|
63
|
+
* To configure any [Custom Attributes](https://api.teamdynamix.com/TDWebApi/Home/type/TeamDynamix.Api.CustomAttributes.CustomAttribute) for the asset.
|
64
|
+
* It must contain expected value for 'id' and 'value' fields.
|
65
|
+
* rest of the fields are optional, check the Custom Attribute's definition for what other fields are updatable.
|
66
|
+
* String interpolation is supported for custom attribute's value.
|
67
|
+
|
68
|
+
[:fields]
|
69
|
+
* The keys are the display title and the values are the methods that are actually called to produce the value.
|
70
|
+
* A link to the asset in Teamdynamix is displayed by default, as first field labelled as URI.
|
71
|
+
* Nested attributes i.e custom attributes can be configured as mentioned in example configuration.
|
72
|
+
* If an attribute or nested attribute does not exist or is not found, it would simply not be displayed.
|
73
|
+
|
74
|
+
## Add additional host attribute
|
75
|
+
```
|
76
|
+
rake db:migrate
|
77
|
+
```
|
78
|
+
|
79
|
+
## Verify the TeamDynamix Tab is loaded
|
80
|
+
Navigate to /hosts/, click on one of the listed host. There should be tabs: 'Properties', 'Metrics', 'Templates', 'NICs' and 'teamdynamix.title or Team Dynamix Tab'
|
81
|
+
|
82
|
+
## Development mode
|
83
|
+
foreman running locally (i.e not installed via rpm/debian package) does not use settings from /etc/foreman/plugins/
|
84
|
+
Add the teamdynamix config to <foreman_repo>/config/settings.yaml
|
85
|
+
|
86
|
+
## Rake Task
|
87
|
+
```
|
88
|
+
rake hosts:sync_with_teamdynamix
|
89
|
+
```
|
90
|
+
Gets existing assets in TeamDynamix based on search params [:teamdynamix][:api][:search]. Then scans the hosts and sync them with TeamDynamix.
|
91
|
+
* If host has teamdynamix_asset_id, update the corresponding TeamDynamix asset.
|
92
|
+
* If host name matches the asset Name or SerialNumber, update the host and the corresponding TeamDynamix asset.
|
93
|
+
* If host has no matching asset, create an asset in TeamDynamix with configured fields.
|
94
|
+
|
95
|
+
## Test mode
|
96
|
+
```
|
97
|
+
gem install foreman_teamdynamix --dev
|
98
|
+
rake test:foreman_teamdynamix
|
99
|
+
```
|
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 = 'ForemanTeamDynamix'
|
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', __dir__)
|
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 = true
|
34
|
+
end
|
35
|
+
|
36
|
+
task default: :test
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'rubocop/rake_task'
|
40
|
+
RuboCop::RakeTask.new
|
41
|
+
rescue StandardError => _
|
42
|
+
puts 'Rubocop not loaded.'
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default do
|
46
|
+
Rake::Task['rubocop'].execute
|
47
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ForemanTeamdynamix
|
2
|
+
# Example: Plugin's HostsController inherits from Foreman's HostsController
|
3
|
+
module HostsControllerExtensions
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def teamdynamix
|
7
|
+
find_resource
|
8
|
+
render partial: 'foreman_teamdynamix/hosts/teamdynamix', :locals => { :host => @host }
|
9
|
+
rescue ActionView::Template::Error => exception
|
10
|
+
process_ajax_error exception, 'fetch teamdynamix tab information'
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def action_permission
|
16
|
+
if params[:action] == 'teamdynamix'
|
17
|
+
:view
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ForemanTeamdynamix
|
2
|
+
module HostsHelperExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
DEFAULT_TD_PANE_FIELDS = { 'Asset ID' => 'ID',
|
5
|
+
'Owner' => 'OwningCustomerName',
|
6
|
+
'Parent Asset' => 'ParentID' }.freeze
|
7
|
+
|
8
|
+
def teamdynamix_title
|
9
|
+
SETTINGS[:teamdynamix][:title] || 'TeamDynamix'
|
10
|
+
end
|
11
|
+
|
12
|
+
def teamdynamix_fields
|
13
|
+
td_pane_fields = SETTINGS[:teamdynamix][:fields] || DEFAULT_TD_PANE_FIELDS
|
14
|
+
return [[_('Asset'), 'None Associated']] unless @host.teamdynamix_asset_id
|
15
|
+
|
16
|
+
get_teamdynamix_asset(@host.teamdynamix_asset_id)
|
17
|
+
|
18
|
+
# always display a link to the asset
|
19
|
+
fields = [asset_uri]
|
20
|
+
|
21
|
+
td_pane_fields.each do |field_name, asset_attr|
|
22
|
+
asset_attr_val = @asset.key?(asset_attr) ? @asset[asset_attr] : get_nested_attrib_val(asset_attr)
|
23
|
+
fields += [[_(field_name.to_s), asset_attr_val]] if asset_attr_val.present?
|
24
|
+
end
|
25
|
+
fields
|
26
|
+
rescue StandardError => e
|
27
|
+
[[_('Error'), e.message]]
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def asset_uri
|
33
|
+
api_url = SETTINGS[:teamdynamix][:api][:url]
|
34
|
+
uri = api_url.split('api').first + @asset['Uri']
|
35
|
+
[_('URI'), link_to(@asset['Uri'], uri, target: '_blank')]
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_teamdynamix_asset(asset_id)
|
39
|
+
@asset = @host.td_api.get_asset(asset_id)
|
40
|
+
rescue StandardError => e
|
41
|
+
raise "Error getting asset Data from Team Dynamix: #{e.message}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_nested_attrib_val(nested_attrib)
|
45
|
+
nested_attrib_tokens = nested_attrib.split('.')
|
46
|
+
parent_attrib = nested_attrib_tokens.first
|
47
|
+
child_attrib = nested_attrib_tokens[1..nested_attrib_tokens.length].join('.')
|
48
|
+
return '' if parent_attrib.blank? || child_attrib.blank?
|
49
|
+
child_attrib.delete!("'")
|
50
|
+
parent_attrib_val = @asset[parent_attrib]
|
51
|
+
return '' if parent_attrib_val.blank?
|
52
|
+
nested_attrib_val = parent_attrib_val.select { |attrib| attrib['Name'] == child_attrib }
|
53
|
+
return '' if nested_attrib_val.blank?
|
54
|
+
nested_attrib_val[0]['Value']
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ForemanTeamdynamix
|
2
|
+
module HostExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def td_api
|
6
|
+
@td_api ||= TeamdynamixApi.instance
|
7
|
+
end
|
8
|
+
|
9
|
+
included do
|
10
|
+
after_validation :create_teamdynamix_asset, on: :create
|
11
|
+
before_destroy :retire_teamdynamix_asset
|
12
|
+
validates :teamdynamix_asset_id, uniqueness: { :allow_blank => true }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def create_teamdynamix_asset
|
18
|
+
asset = td_api.create_asset(self)
|
19
|
+
self.teamdynamix_asset_id = asset['ID']
|
20
|
+
rescue StandardError => e
|
21
|
+
errors.add(:base, _("Could not create the asset for the host in TeamDynamix: #{e.message}"))
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def retire_teamdynamix_asset
|
26
|
+
td_api.retire_asset(teamdynamix_asset_id) if teamdynamix_asset_id
|
27
|
+
rescue StandardError => e
|
28
|
+
errors.add(:base, _("Could not retire the asset for the host in TeamDynamix: #{e.message}"))
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
if SETTINGS[:teamdynamix]
|
2
|
+
Deface::Override.new(:virtual_path => 'hosts/show',
|
3
|
+
:name => 'create_link',
|
4
|
+
:insert_bottom => 'div#myTabContent',
|
5
|
+
:text =>
|
6
|
+
"\n <div id='teamdynamix' class='tab-pane'
|
7
|
+
data-ajax-url='<%= teamdynamix_host_path(@host)%>' data-on-complete='onContentLoad'>
|
8
|
+
<%= spinner(_('Loading Team Dynamix information for the host ...')) %>
|
9
|
+
</div>")
|
10
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
class TeamdynamixApi
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
if SETTINGS[:teamdynamix].blank?
|
6
|
+
raise('Missing configurations for the plugin see https://github.com/MiamiOH/foreman_teamdynamix')
|
7
|
+
end
|
8
|
+
API_CONFIG = SETTINGS[:teamdynamix][:api]
|
9
|
+
|
10
|
+
raise('Missing Team Dynamix Api ID in plugin settings') if API_CONFIG[:appId].blank?
|
11
|
+
APP_ID = API_CONFIG[:appId]
|
12
|
+
|
13
|
+
raise('Missing Team Dynamix API URL in plugin settings') if API_CONFIG[:url].blank?
|
14
|
+
API_URL = API_CONFIG[:url]
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@auth_token = request_token
|
18
|
+
raise('Invalid authentication token') unless valid_auth_token?(@auth_token)
|
19
|
+
end
|
20
|
+
|
21
|
+
# returns TeamDynamix.Api.Assets.Asset
|
22
|
+
def get_asset(asset_id)
|
23
|
+
uri = URI.parse(API_URL + "/#{APP_ID}/assets/#{asset_id}")
|
24
|
+
rest(:get, uri)
|
25
|
+
end
|
26
|
+
|
27
|
+
def asset_exist?(asset_id)
|
28
|
+
get_asset(asset_id).present?
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_asset(host)
|
32
|
+
uri = URI.parse(API_URL + "/#{APP_ID}/assets")
|
33
|
+
rest(:post, uri, create_asset_payload(host))
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_asset(host)
|
37
|
+
uri = URI.parse(API_URL + "/#{APP_ID}/assets/#{host.teamdynamix_asset_id}")
|
38
|
+
rest(:post, uri, update_asset_payload(host))
|
39
|
+
end
|
40
|
+
|
41
|
+
def retire_asset(asset_id)
|
42
|
+
uri = URI.parse(API_URL + "/#{APP_ID}/assets/#{asset_id}")
|
43
|
+
rest(:post, uri, retire_asset_payload(asset_id))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Gets a list of assets matching the specified criteria. (IEnumerable(Of TeamDynamix.Api.Assets.Asset))
|
47
|
+
def search_asset(search_params)
|
48
|
+
uri = URI.parse(API_URL + "/#{APP_ID}/assets/search")
|
49
|
+
rest(:post, uri, search_params)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def rest(method, uri, payload = nil)
|
55
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
56
|
+
http.use_ssl = true
|
57
|
+
# set verb, payload and headers
|
58
|
+
if method == :post
|
59
|
+
req = Net::HTTP::Post.new(uri)
|
60
|
+
req.add_field('Content-Type', 'application/json')
|
61
|
+
req.body = payload.to_json
|
62
|
+
else
|
63
|
+
req = Net::HTTP::Get.new(uri)
|
64
|
+
end
|
65
|
+
# set headers
|
66
|
+
req.add_field('Authorization', 'Bearer ' + @auth_token)
|
67
|
+
# send request
|
68
|
+
res = http.start do |http_handler|
|
69
|
+
http_handler.request(req)
|
70
|
+
end
|
71
|
+
# return response
|
72
|
+
parse_response(res)
|
73
|
+
end
|
74
|
+
|
75
|
+
def request_token
|
76
|
+
uri = URI.parse(API_URL + '/auth')
|
77
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
78
|
+
http.use_ssl = true
|
79
|
+
# set verb
|
80
|
+
req = Net::HTTP::Post.new(uri)
|
81
|
+
# set headers
|
82
|
+
req.add_field('Content-Type', 'application/json')
|
83
|
+
# set payload
|
84
|
+
req.body = { username: API_CONFIG[:username],
|
85
|
+
password: API_CONFIG[:password] }.to_json
|
86
|
+
# send request
|
87
|
+
res = http.start do |http_handler|
|
88
|
+
http_handler.request(req)
|
89
|
+
end
|
90
|
+
# return response
|
91
|
+
parse_response(res)
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_response(res)
|
95
|
+
begin
|
96
|
+
res_body = JSON.parse(res.body)
|
97
|
+
rescue JSON::ParserError
|
98
|
+
res_body = res.body
|
99
|
+
end
|
100
|
+
case res.code
|
101
|
+
when /20(.)/ then res_body
|
102
|
+
else
|
103
|
+
raise({ status: res.code, msg: res.msg, body: res_body }.to_json)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def retire_asset_payload(asset_id)
|
108
|
+
asset = get_asset(asset_id)
|
109
|
+
asset.merge(API_CONFIG[:delete].stringify_keys)
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_asset_payload(host)
|
113
|
+
ensure_configured_create_params
|
114
|
+
default_attrs = { AppID: APP_ID,
|
115
|
+
SerialNumber: host.name,
|
116
|
+
Name: host.fqdn }
|
117
|
+
create_attrs = API_CONFIG[:create].symbolize_keys
|
118
|
+
evaluate_attributes(create_attrs)
|
119
|
+
default_attrs.merge(create_attrs)
|
120
|
+
end
|
121
|
+
|
122
|
+
def evaluate_attributes(create_attrs)
|
123
|
+
return if create_attrs[:Attributes].blank?
|
124
|
+
create_attrs[:Attributes].each do |attribute|
|
125
|
+
attribute.transform_keys!(&:downcase)
|
126
|
+
attribute['value'] = eval("\"#{attribute['value']}\"", binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def must_configure_create_params
|
131
|
+
[:StatusID]
|
132
|
+
end
|
133
|
+
|
134
|
+
def valid_auth_token?(token)
|
135
|
+
token.match(/^[a-zA-Z0-9\.\-\_]*$/)
|
136
|
+
end
|
137
|
+
|
138
|
+
def update_asset_payload(host)
|
139
|
+
payload = { ID: host.teamdynamix_asset_id }
|
140
|
+
payload.merge(create_asset_payload(host))
|
141
|
+
end
|
142
|
+
|
143
|
+
def ensure_configured_create_params
|
144
|
+
must_configure_create_params.each do |must_configure_param|
|
145
|
+
unless API_CONFIG[:create].include?(must_configure_param)
|
146
|
+
raise("#{must_configure_param} is required. Set it as a configuration item.")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<table id="teamdynamix_table" class="<%= table_css_classes %>">
|
2
|
+
<thead>
|
3
|
+
<tr>
|
4
|
+
<th colspan="2"><%= _(teamdynamix_title) %></th>
|
5
|
+
</tr>
|
6
|
+
</thead>
|
7
|
+
<tbody>
|
8
|
+
<% teamdynamix_fields.each do |name, value| %>
|
9
|
+
<tr>
|
10
|
+
<td><%= name %></td>
|
11
|
+
<td><%= value %></td>
|
12
|
+
</tr>
|
13
|
+
<% end %>
|
14
|
+
</tbody>
|
15
|
+
</table>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'deface'
|
2
|
+
module ForemanTeamdynamix
|
3
|
+
class Engine < ::Rails::Engine
|
4
|
+
engine_name 'foreman_teamdynamix'
|
5
|
+
|
6
|
+
config.autoload_paths += Dir["#{config.root}/app/controllers/concerns"]
|
7
|
+
config.autoload_paths += Dir["#{config.root}/app/helpers/concerns"]
|
8
|
+
config.autoload_paths += Dir["#{config.root}/app/models/concerns"]
|
9
|
+
config.autoload_paths += Dir["#{config.root}/app/overrides"]
|
10
|
+
|
11
|
+
initializer 'foreman_teamdynamix.load_app_instance_data' do |app|
|
12
|
+
if ForemanTeamdynamix::Engine.paths['db/migrate'].existent
|
13
|
+
app.config.paths['db/migrate'].concat(ForemanTeamdynamix::Engine.paths['db/migrate'].to_a)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
initializer 'foreman_teamdynamix.register_plugin', :before => :finisher_hook do |_app|
|
18
|
+
Foreman::Plugin.register :foreman_teamdynamix do
|
19
|
+
requires_foreman '>= 1.7'
|
20
|
+
|
21
|
+
# Add permissions
|
22
|
+
security_block :foreman_teamdynamix do
|
23
|
+
ps = permission :view_hosts,
|
24
|
+
{ :hosts => [:teamdynamix] },
|
25
|
+
:resource_type => 'Host'
|
26
|
+
pn = ps.pop
|
27
|
+
po = ps.detect { |p| p.name == :view_hosts }
|
28
|
+
po.actions << pn.actions.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Include concerns in this config.to_prepare block
|
34
|
+
config.to_prepare do
|
35
|
+
begin
|
36
|
+
HostsHelper.send(:include, ForemanTeamdynamix::HostsHelperExtensions)
|
37
|
+
::HostsController.send(:include, ForemanTeamdynamix::HostsControllerExtensions)
|
38
|
+
::Host::Managed.send(:include, ForemanTeamdynamix::HostExtensions)
|
39
|
+
rescue StandardError => e
|
40
|
+
Rails.logger.warn "ForemanTeamdynamix: skipping engine hook (#{e})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
initializer 'foreman_teamdynamix.register_gettext', after: :load_config_initializers do |_app|
|
45
|
+
locale_dir = File.join(File.expand_path('../..', __dir__), 'locale')
|
46
|
+
locale_domain = 'foreman_teamdynamix'
|
47
|
+
Foreman::Gettext::Support.add_text_domain locale_domain, locale_dir
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Tests
|
2
|
+
namespace :test do
|
3
|
+
desc 'Test Foreman Team Dynamix'
|
4
|
+
Rake::TestTask.new(:foreman_teamdynamix) do |t|
|
5
|
+
test_dir = File.join(File.dirname(__FILE__), '../..', 'test')
|
6
|
+
t.libs << ['test', test_dir]
|
7
|
+
t.pattern = "#{test_dir}/**/*_test.rb"
|
8
|
+
t.verbose = true
|
9
|
+
t.warning = false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :foreman_teamdynamix do
|
14
|
+
task :rubocop do
|
15
|
+
begin
|
16
|
+
require 'rubocop/rake_task'
|
17
|
+
RuboCop::RakeTask.new(:rubocop_foreman_teamdynamix) do |task|
|
18
|
+
task.patterns = ["#{ForemanTeamdynamix::Engine.root}/app/**/*.rb",
|
19
|
+
"#{ForemanTeamdynamix::Engine.root}/lib/**/*.rb",
|
20
|
+
"#{ForemanTeamdynamix::Engine.root}/test/**/*.rb"]
|
21
|
+
end
|
22
|
+
rescue StandardError => e
|
23
|
+
puts "Rubocop not loaded #{e.message}"
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::Task['rubocop_foreman_teamdynamix'].invoke
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::Task[:test].enhance do
|
31
|
+
Rake::Task['test:foreman_custom_tab'].invoke
|
32
|
+
end
|
33
|
+
|
34
|
+
load 'tasks/jenkins.rake'
|
35
|
+
if Rake::Task.task_defined?(:'jenkins:unit')
|
36
|
+
Rake::Task['jenkins:unit'].enhance do
|
37
|
+
Rake::Task['test:foreman_custom_tab'].invoke
|
38
|
+
Rake::Task['foreman_custom_tab:rubocop'].invoke
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
desc <<-DESC.strip_heredoc.squish
|
2
|
+
Scans existing hosts and creates or updates the asset in TeamDynamix.
|
3
|
+
* If found, update the fields in the TeamDynamix asset.
|
4
|
+
* If not found, create a TeamDynamix asset with desired fields.
|
5
|
+
* If host does not have a teamdynamix_asset_id or it is deleted via backend, it creates a new asset.
|
6
|
+
|
7
|
+
It could be run for all the hosts as:
|
8
|
+
* rake hosts:sync_with_teamdynamix
|
9
|
+
|
10
|
+
Or for specific hosts as (No space b/w hostnames):
|
11
|
+
* rake hosts:sync_with_teamdynamix[hostname1,hostname2,..,hostnameX]
|
12
|
+
DESC
|
13
|
+
namespace :hosts do
|
14
|
+
task :sync_with_teamdynamix => :environment do |_task|
|
15
|
+
@td_api = TeamdynamixApi.instance
|
16
|
+
@errors = []
|
17
|
+
@hosts_synced = []
|
18
|
+
|
19
|
+
set_current_user
|
20
|
+
|
21
|
+
sync_existing_assets_to_hosts
|
22
|
+
|
23
|
+
create_assets_for_unmapped_hosts
|
24
|
+
|
25
|
+
print_summary
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_current_user
|
29
|
+
console_user = User.find_by(login: 'foreman_console_admin')
|
30
|
+
User.current = console_user
|
31
|
+
end
|
32
|
+
|
33
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
34
|
+
def sync_existing_assets_to_hosts
|
35
|
+
# sending empty search param to get all the assets in TD at once
|
36
|
+
# Note: due to a limitation(possibly a bug) in TD search asset api
|
37
|
+
# each and every search payload returns all the assets.
|
38
|
+
search_params = SETTINGS[:teamdynamix][:api][:search] || {}
|
39
|
+
@teamdynamix_assets = @td_api.search_asset(search_params)
|
40
|
+
@teamdynamix_assets.each do |asset|
|
41
|
+
asset_id = asset['ID']
|
42
|
+
# WHEN Asset is already mapped to a host
|
43
|
+
# THEN update the asset in TD as per current configuration
|
44
|
+
host = Host.find_by(teamdynamix_asset_id: asset_id)
|
45
|
+
if host.present?
|
46
|
+
@hosts_synced << host.id
|
47
|
+
@td_api.update_asset(host)
|
48
|
+
next
|
49
|
+
end
|
50
|
+
hosts = Host.where(name: [asset['Name'], asset['SerialNumber']])
|
51
|
+
# WHEN Asset does not have a matching host, THEN Do nothing
|
52
|
+
next if hosts.blank?
|
53
|
+
# WHEN Asset has more than one matching host, THEN report error
|
54
|
+
if hosts.count > 1
|
55
|
+
@errors << "#{hosts.count} matching hosts found for asset with ID #{asset['ID']}"
|
56
|
+
next
|
57
|
+
end
|
58
|
+
# WHEN Asset has a uniquely matching host which is not yet synced
|
59
|
+
# THEN update the host with the asset ID
|
60
|
+
# AND update the asset in TD as per current configuration
|
61
|
+
host = hosts.first
|
62
|
+
@hosts_synced << host.id
|
63
|
+
host.teamdynamix_asset_id = asset_id
|
64
|
+
@errors << "failed to update host ##{host.id} for asset ID ##{asset_id}" unless host.save
|
65
|
+
@td_api.update_asset(host)
|
66
|
+
end
|
67
|
+
rescue StandardError => e
|
68
|
+
@errors << "component: syncing_assets_to_hosts, asset_id: #{asset['ID']},
|
69
|
+
asset_name: #{asset['Name']}, Error: #{e.message}"
|
70
|
+
end
|
71
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
72
|
+
|
73
|
+
# sync hosts that do not have assets created in Teamdynamix
|
74
|
+
def create_assets_for_unmapped_hosts
|
75
|
+
@unmapped_hosts = Host.where.not(id: @hosts_synced)
|
76
|
+
@unmapped_hosts.each do |host|
|
77
|
+
asset = @td_api.create_asset(host)
|
78
|
+
host.teamdynamix_asset_id = asset['ID']
|
79
|
+
@errors << "failed to update host ##{host.id} for asset ID ##{asset_id}" unless host.save
|
80
|
+
end
|
81
|
+
rescue StandardError => e
|
82
|
+
@errors << "component: creating_new_assets, host: #{host.id}, hostname: #{host.name}, Error: #{e.message}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def print_summary
|
86
|
+
puts "\n Summary:"
|
87
|
+
puts "\t Total Assets in TD: #{@teamdynamix_assets.count}"
|
88
|
+
puts "\t Hosts with matching asset: #{@hosts_synced.uniq.count}"
|
89
|
+
puts "\t Hosts with no assets: #{@unmapped_hosts}" if @unmapped_hosts.present?
|
90
|
+
return if @errors.blank?
|
91
|
+
puts "\n Errors: #{@errors.count}"
|
92
|
+
@errors.each do |error|
|
93
|
+
puts "\t#{error}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|