foreman_teamdynamix 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ if SETTINGS[:teamdynamix]
2
+ Deface::Override.new(:virtual_path => 'hosts/show',
3
+ :name => 'add_tab_link',
4
+ :insert_bottom => 'ul#myTab',
5
+ :text =>
6
+ "<li><a href='#teamdynamix' data-toggle='tab'><%= _(teamdynamix_title) %></a></li>")
7
+ 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,9 @@
1
+ Rails.application.routes.draw do
2
+ constraints(:id => %r{[^\/]+}) do
3
+ resources :hosts do
4
+ member do
5
+ get 'teamdynamix'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class AddTeamdynamixAssetIdToHosts < ActiveRecord::Migration
2
+ def change
3
+ add_column :hosts, :teamdynamix_asset_id, :string
4
+ end
5
+ end
@@ -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,3 @@
1
+ module ForemanTeamdynamix
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'foreman_teamdynamix/engine'
2
+
3
+ module ForemanTeamdynamix
4
+ 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