foreman_teamdynamix 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +17 -16
- data/app/helpers/concerns/foreman_teamdynamix/hosts_helper_extensions.rb +4 -10
- data/app/models/concerns/foreman_teamdynamix/host_extensions.rb +38 -17
- data/app/services/teamdynamix_api.rb +37 -33
- data/lib/foreman_teamdynamix/version.rb +1 -1
- data/lib/tasks/teamdynamix.rake +22 -28
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ce4c2a7a334ce842ffc0d301d3bb3698eec89e0
|
4
|
+
data.tar.gz: 1e757e66050495e84c00be995dc3a6db5ff78433
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afc16bd54e8964593702e2dec8f412b1ccf3b031817d77a3ecc0cbb542fdeb05e97656719ab512b6d46d572c493f5e2741e26b0341e6fa24a89ac9fe9ece3b14
|
7
|
+
data.tar.gz: 83e332682132dd98bb7babae29789cccdd10a1144910992c653b64bb768c44e57719a7db8cce2db2b6a3b1ded201e94a6f34f05d6dd03af959e1ba28e3335037
|
data/README.md
CHANGED
@@ -28,23 +28,23 @@ Example Configuration
|
|
28
28
|
:username: 'xxxxxx'
|
29
29
|
:password: 'xxxxxx'
|
30
30
|
:create:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
-
|
36
|
-
|
37
|
-
|
38
|
-
-
|
39
|
-
|
40
|
-
|
31
|
+
StatusID: 641
|
32
|
+
AcquisitionDate: host.created_at
|
33
|
+
OwningCustomerName: "'foreman_teamdynamix_plugin_test'"
|
34
|
+
Attributes:
|
35
|
+
- Name: mu.ci.Lifecycle Status
|
36
|
+
ID: 11634
|
37
|
+
Value: 26190
|
38
|
+
- Name: mu.ci.Description
|
39
|
+
ID: 11632
|
40
|
+
Value: >-
|
41
41
|
"created by ForemanTeamdynamix plugin, owner is #{host.owner_id}"
|
42
|
-
-
|
43
|
-
|
44
|
-
|
42
|
+
- Name: Ticket Routing Details
|
43
|
+
ID: 11636
|
44
|
+
Value: >-
|
45
45
|
"Asset for host running on OS #{host.operatingsystem_id}"
|
46
46
|
:delete:
|
47
|
-
|
47
|
+
StatusId: 642
|
48
48
|
:fields:
|
49
49
|
:url: https://miamioh.teamdynamix.com/SBTDNext/Apps
|
50
50
|
Asset ID: ID
|
@@ -58,9 +58,10 @@ Example Configuration
|
|
58
58
|
* All attributes are passed to the TeamDynamix API as is, while creating or deleting a TeamDynamix Asset.
|
59
59
|
* An asset gets created or deleted with the Foreman Host create or delete life cycle event.
|
60
60
|
|
61
|
-
[:api][:create][
|
61
|
+
[:api][:create][Attributes]
|
62
62
|
* To configure any [Custom Attributes](https://api.teamdynamix.com/TDWebApi/Home/type/TeamDynamix.Api.CustomAttributes.CustomAttribute) for the asset.
|
63
|
-
* It must contain expected value for '
|
63
|
+
* It must contain expected value for 'Name', 'ID' and 'Value' fields.
|
64
|
+
* Notice the case of 'Name', 'ID' and 'Value', this must match for correct merging.
|
64
65
|
* rest of the fields are optional, check the Custom Attribute's definition for what other fields are updatable.
|
65
66
|
* Code evaluation is supported for custom attribute's value.
|
66
67
|
|
@@ -11,9 +11,9 @@ module ForemanTeamdynamix
|
|
11
11
|
|
12
12
|
def teamdynamix_fields
|
13
13
|
td_pane_fields = SETTINGS[:teamdynamix][:fields] || DEFAULT_TD_PANE_FIELDS
|
14
|
-
return [[_('Asset'), 'None Associated']] unless @host.
|
14
|
+
return [[_('Asset'), 'None Associated or error from Team Dynamix']] unless @host.teamdynamix_asset
|
15
15
|
|
16
|
-
|
16
|
+
@asset = @host.teamdynamix_asset
|
17
17
|
|
18
18
|
# display a link to the asset if url set
|
19
19
|
fields = asset_uri(td_pane_fields)
|
@@ -38,12 +38,6 @@ module ForemanTeamdynamix
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def get_teamdynamix_asset(asset_id)
|
42
|
-
@asset = @host.td_api.get_asset(asset_id)
|
43
|
-
rescue StandardError => e
|
44
|
-
raise "Error getting asset Data from Team Dynamix: #{e.message}"
|
45
|
-
end
|
46
|
-
|
47
41
|
def get_nested_attrib_val(nested_attrib)
|
48
42
|
nested_attrib_tokens = nested_attrib.split('.')
|
49
43
|
parent_attrib = nested_attrib_tokens.first
|
@@ -52,9 +46,9 @@ module ForemanTeamdynamix
|
|
52
46
|
child_attrib.delete!("'")
|
53
47
|
parent_attrib_val = @asset[parent_attrib]
|
54
48
|
return '' if parent_attrib_val.blank?
|
55
|
-
nested_attrib_val = parent_attrib_val.
|
49
|
+
nested_attrib_val = parent_attrib_val.find { |attrib| attrib['Name'] == child_attrib }
|
56
50
|
return '' if nested_attrib_val.blank?
|
57
|
-
nested_attrib_val[
|
51
|
+
nested_attrib_val['Value']
|
58
52
|
end
|
59
53
|
end
|
60
54
|
end
|
@@ -2,38 +2,59 @@ module ForemanTeamdynamix
|
|
2
2
|
module HostExtensions
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
+
included do
|
6
|
+
before_create :create_or_update_teamdynamix_asset
|
7
|
+
before_destroy :retire_teamdynamix_asset
|
8
|
+
validates :teamdynamix_asset_uid, uniqueness: { :allow_blank => true }
|
9
|
+
end
|
10
|
+
|
5
11
|
def td_api
|
6
12
|
@td_api ||= TeamdynamixApi.instance
|
7
13
|
end
|
8
14
|
|
9
|
-
|
10
|
-
|
11
|
-
before_destroy :retire_teamdynamix_asset
|
12
|
-
validates :teamdynamix_asset_uid, uniqueness: { :allow_blank => true }
|
15
|
+
def teamdynamix_asset_status
|
16
|
+
@teamdynamix_asset_status
|
13
17
|
end
|
14
18
|
|
15
|
-
|
19
|
+
def teamdynamix_asset(search = false)
|
20
|
+
@teamdynamix_asset ||= td_api.get_asset(teamdynamix_asset_uid)
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
if search && !@teamdynamix_asset
|
23
|
+
assets = td_api.search_asset(SerialLike: name)
|
24
|
+
if assets.length == 1
|
25
|
+
@teamdynamix_asset = td_api.get_asset(assets.first['ID'])
|
26
|
+
self.teamdynamix_asset_uid = teamdynamix_asset['ID']
|
27
|
+
@teamdynamix_asset_status = :updated_search
|
28
|
+
elsif assets.length > 1
|
29
|
+
errors.add(:base, _("Search for asset in TeamDynamix failed: Found #{assets.length} matching assets"))
|
30
|
+
end
|
31
|
+
end
|
20
32
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
else
|
27
|
-
self.teamdynamix_asset_uid = assets.first['ID']
|
33
|
+
@teamdynamix_asset
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_or_update_teamdynamix_asset(save = false)
|
37
|
+
if teamdynamix_asset(true)
|
28
38
|
td_api.update_asset(self)
|
39
|
+
@teamdynamix_asset_status ||= :updated_id
|
40
|
+
self.save if save
|
41
|
+
elsif errors.empty?
|
42
|
+
@teamdynamix_asset = td_api.create_asset(self)
|
43
|
+
self.teamdynamix_asset_uid = teamdynamix_asset['ID']
|
44
|
+
@teamdynamix_asset_status = :created
|
45
|
+
self.save if save
|
46
|
+
else
|
47
|
+
false
|
29
48
|
end
|
30
49
|
rescue StandardError => e
|
31
|
-
errors.add(:base, _("Could not create the asset for the host in TeamDynamix: #{e.message}"))
|
50
|
+
errors.add(:base, _("Could not create or update the asset for the host in TeamDynamix: #{e.message}"))
|
32
51
|
false
|
33
52
|
end
|
34
53
|
|
54
|
+
private
|
55
|
+
|
35
56
|
def retire_teamdynamix_asset
|
36
|
-
td_api.retire_asset(
|
57
|
+
td_api.retire_asset(self) if teamdynamix_asset
|
37
58
|
rescue StandardError => e
|
38
59
|
errors.add(:base, _("Could not retire the asset for the host in TeamDynamix: #{e.message}"))
|
39
60
|
false
|
@@ -20,14 +20,11 @@ class TeamdynamixApi
|
|
20
20
|
|
21
21
|
# returns TeamDynamix.Api.Assets.Asset
|
22
22
|
def get_asset(asset_id)
|
23
|
+
return nil unless asset_id
|
23
24
|
uri = URI.parse("#{API_URL}/#{APP_ID}/assets/#{asset_id}")
|
24
25
|
rest(:get, uri)
|
25
|
-
end
|
26
|
-
|
27
|
-
def asset_exist?(asset_id)
|
28
|
-
get_asset(asset_id).present?
|
29
26
|
rescue RuntimeError
|
30
|
-
|
27
|
+
nil
|
31
28
|
end
|
32
29
|
|
33
30
|
def create_asset(host)
|
@@ -40,9 +37,9 @@ class TeamdynamixApi
|
|
40
37
|
rest(:post, uri, update_asset_payload(host))
|
41
38
|
end
|
42
39
|
|
43
|
-
def retire_asset(
|
44
|
-
uri = URI.parse("#{API_URL}/#{APP_ID}/assets/#{
|
45
|
-
rest(:post, uri, retire_asset_payload(
|
40
|
+
def retire_asset(host)
|
41
|
+
uri = URI.parse("#{API_URL}/#{APP_ID}/assets/#{host.teamdynamix_asset_uid}")
|
42
|
+
rest(:post, uri, retire_asset_payload(host))
|
46
43
|
end
|
47
44
|
|
48
45
|
# Gets a list of assets matching the specified criteria. (IEnumerable(Of TeamDynamix.Api.Assets.Asset))
|
@@ -106,26 +103,38 @@ class TeamdynamixApi
|
|
106
103
|
end
|
107
104
|
end
|
108
105
|
|
109
|
-
def retire_asset_payload(asset_id)
|
110
|
-
asset = get_asset(asset_id)
|
111
|
-
asset.merge(API_CONFIG[:delete].stringify_keys)
|
112
|
-
end
|
113
|
-
|
114
106
|
def create_asset_payload(host)
|
115
107
|
ensure_configured_create_params
|
116
|
-
default_attrs = { AppID
|
117
|
-
SerialNumber
|
118
|
-
Name
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
108
|
+
default_attrs = { 'AppID' => APP_ID,
|
109
|
+
'SerialNumber' => host.name,
|
110
|
+
'Name' => host.fqdn }
|
111
|
+
evaluate_attributes(API_CONFIG[:create], host, default_attrs)
|
112
|
+
end
|
113
|
+
|
114
|
+
def update_asset_payload(host)
|
115
|
+
default_attrs = { 'AppID' => APP_ID,
|
116
|
+
'SerialNumber' => host.name,
|
117
|
+
'Name' => host.fqdn }
|
118
|
+
evaluate_attributes(API_CONFIG[:create], host, host.teamdynamix_asset.merge(default_attrs))
|
119
|
+
end
|
120
|
+
|
121
|
+
def retire_asset_payload(host)
|
122
|
+
evaluate_attributes(API_CONFIG[:delete], host, host.teamdynamix_asset)
|
123
|
+
end
|
124
|
+
|
125
|
+
def evaluate_attributes(attrs, host, asset = {})
|
126
|
+
attrs.stringify_keys.each_with_object(asset) do |(k, v), h|
|
127
|
+
if k.eql?('Attributes')
|
128
|
+
h['Attributes'] ||= []
|
129
|
+
v.each do |attrib|
|
130
|
+
attrib_c = attrib.stringify_keys
|
131
|
+
match = h['Attributes'].find { |attrib_a| attrib_a['Name'] == attrib_c['Name'] }
|
132
|
+
if match
|
133
|
+
match['Value'] = eval_attribute(attrib_c['Value'], host)
|
134
|
+
else
|
135
|
+
attrib_c['Value'] = eval_attribute(attrib_c['Value'], host)
|
136
|
+
h['Attributes'] << attrib_c
|
137
|
+
end
|
129
138
|
end
|
130
139
|
else
|
131
140
|
h[k] = eval_attribute(v, host)
|
@@ -140,21 +149,16 @@ class TeamdynamixApi
|
|
140
149
|
end
|
141
150
|
|
142
151
|
def must_configure_create_params
|
143
|
-
[
|
152
|
+
['StatusID']
|
144
153
|
end
|
145
154
|
|
146
155
|
def valid_auth_token?(token)
|
147
156
|
token.match(/^[a-zA-Z0-9\.\-\_]*$/)
|
148
157
|
end
|
149
158
|
|
150
|
-
def update_asset_payload(host)
|
151
|
-
asset = get_asset(host.teamdynamix_asset_uid)
|
152
|
-
asset.merge(create_asset_payload(host))
|
153
|
-
end
|
154
|
-
|
155
159
|
def ensure_configured_create_params
|
156
160
|
must_configure_create_params.each do |must_configure_param|
|
157
|
-
unless API_CONFIG[:create].include?(must_configure_param)
|
161
|
+
unless API_CONFIG[:create].include?(must_configure_param) || API_CONFIG[:create].include?(must_configure_param.to_sym)
|
158
162
|
raise("#{must_configure_param} is required. Set it as a configuration item.")
|
159
163
|
end
|
160
164
|
end
|
data/lib/tasks/teamdynamix.rake
CHANGED
@@ -6,47 +6,41 @@ desc <<-DESC.strip_heredoc.squish
|
|
6
6
|
It could be run for all the hosts as:
|
7
7
|
* rake teamydynamix:sync:hosts
|
8
8
|
|
9
|
+
Available options:
|
10
|
+
* where => where string for limiting host query
|
11
|
+
* limit => limit for limiting host query
|
12
|
+
|
9
13
|
DESC
|
10
14
|
namespace :teamdynamix do
|
11
15
|
namespace :sync do
|
12
16
|
task :hosts => :environment do
|
13
|
-
td_api = TeamdynamixApi.instance
|
14
17
|
errors = []
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
+
created = 0
|
19
|
+
updated_search = 0
|
20
|
+
updated_id = 0
|
18
21
|
|
19
22
|
console_user = User.find_by(login: 'foreman_console_admin')
|
20
23
|
User.current = console_user
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
errors.push("Could not save host: #{h.name} (#{h.id})") unless h.save
|
33
|
-
creates += 1
|
34
|
-
elsif assets.length > 1
|
35
|
-
errors.push("Could not sync: Found more than 1 asset for #{h.name} (#{h.id})")
|
36
|
-
else
|
37
|
-
h.teamdynamix_asset_uid = assets.first['ID']
|
38
|
-
td_api.update_asset(h)
|
39
|
-
errors.push("Could not save host: #{h.name} (#{h.id})") unless h.save
|
40
|
-
updates_from_serial_matching += 1
|
25
|
+
hosts = Host
|
26
|
+
hosts = hosts.where(ENV['where']) if ENV['where']
|
27
|
+
hosts = hosts.limit(ENV['limit']) if ENV['limit']
|
28
|
+
|
29
|
+
hosts.all.each do |h|
|
30
|
+
if h.create_or_update_teamdynamix_asset(true)
|
31
|
+
case h.teamdynamix_asset_status
|
32
|
+
when :created then created += 1
|
33
|
+
when :updated_search then updated_search += 1
|
34
|
+
when :updated_id then updated_id += 1
|
41
35
|
end
|
36
|
+
else
|
37
|
+
errors.push("Could not save host: #{h.name} (#{h.id}):\n #{h.errors.full_messages.join("\n ")}")
|
42
38
|
end
|
43
39
|
sleep(1.5) # TD only allows 60 api calls per minute
|
44
40
|
end
|
45
|
-
puts "Assets created: #{
|
46
|
-
unless
|
47
|
-
|
48
|
-
end
|
49
|
-
puts "Assets updated from ID: #{update_from_asset_id}" unless update_from_asset_id.eql?(0)
|
41
|
+
puts "Assets created: #{created}" unless created.eql?(0)
|
42
|
+
puts "Assets updated from serial search: #{updated_search}" unless updated_search.eql?(0)
|
43
|
+
puts "Assets updated from ID: #{updated_id}" unless updated_id.eql?(0)
|
50
44
|
puts "Errors:\n#{errors.join("\n")}" unless errors.empty?
|
51
45
|
end
|
52
46
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_teamdynamix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nipendar Tyagi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deface
|