wavefront-sdk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +20 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +1157 -0
  5. data/.travis.yml +16 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +58 -0
  8. data/LICENSE.txt +27 -0
  9. data/README.md +103 -0
  10. data/Rakefile +18 -0
  11. data/lib/wavefront-sdk/alert.rb +195 -0
  12. data/lib/wavefront-sdk/base.rb +251 -0
  13. data/lib/wavefront-sdk/cloudintegration.rb +88 -0
  14. data/lib/wavefront-sdk/credentials.rb +79 -0
  15. data/lib/wavefront-sdk/dashboard.rb +157 -0
  16. data/lib/wavefront-sdk/event.rb +173 -0
  17. data/lib/wavefront-sdk/exception.rb +39 -0
  18. data/lib/wavefront-sdk/externallink.rb +77 -0
  19. data/lib/wavefront-sdk/maintenancewindow.rb +75 -0
  20. data/lib/wavefront-sdk/message.rb +36 -0
  21. data/lib/wavefront-sdk/metric.rb +52 -0
  22. data/lib/wavefront-sdk/mixins.rb +60 -0
  23. data/lib/wavefront-sdk/proxy.rb +95 -0
  24. data/lib/wavefront-sdk/query.rb +96 -0
  25. data/lib/wavefront-sdk/response.rb +56 -0
  26. data/lib/wavefront-sdk/savedsearch.rb +88 -0
  27. data/lib/wavefront-sdk/search.rb +58 -0
  28. data/lib/wavefront-sdk/source.rb +131 -0
  29. data/lib/wavefront-sdk/user.rb +108 -0
  30. data/lib/wavefront-sdk/validators.rb +395 -0
  31. data/lib/wavefront-sdk/version.rb +1 -0
  32. data/lib/wavefront-sdk/webhook.rb +73 -0
  33. data/lib/wavefront-sdk/write.rb +225 -0
  34. data/pkg/wavefront-client-3.5.0.gem +0 -0
  35. data/spec/.rubocop.yml +14 -0
  36. data/spec/spec_helper.rb +157 -0
  37. data/spec/wavefront-sdk/alert_spec.rb +83 -0
  38. data/spec/wavefront-sdk/base_spec.rb +88 -0
  39. data/spec/wavefront-sdk/cloudintegration_spec.rb +54 -0
  40. data/spec/wavefront-sdk/credentials_spec.rb +55 -0
  41. data/spec/wavefront-sdk/dashboard_spec.rb +74 -0
  42. data/spec/wavefront-sdk/event_spec.rb +83 -0
  43. data/spec/wavefront-sdk/externallink_spec.rb +65 -0
  44. data/spec/wavefront-sdk/maintenancewindow_spec.rb +48 -0
  45. data/spec/wavefront-sdk/message_spec.rb +19 -0
  46. data/spec/wavefront-sdk/metric_spec.rb +21 -0
  47. data/spec/wavefront-sdk/mixins_spec.rb +27 -0
  48. data/spec/wavefront-sdk/proxy_spec.rb +41 -0
  49. data/spec/wavefront-sdk/query_spec.rb +51 -0
  50. data/spec/wavefront-sdk/resources/test.conf +10 -0
  51. data/spec/wavefront-sdk/response_spec.rb +47 -0
  52. data/spec/wavefront-sdk/savedsearch_spec.rb +54 -0
  53. data/spec/wavefront-sdk/search_spec.rb +47 -0
  54. data/spec/wavefront-sdk/source_spec.rb +48 -0
  55. data/spec/wavefront-sdk/user_spec.rb +56 -0
  56. data/spec/wavefront-sdk/validators_spec.rb +238 -0
  57. data/spec/wavefront-sdk/webhook_spec.rb +50 -0
  58. data/spec/wavefront-sdk/write_spec.rb +167 -0
  59. data/wavefront-sdk.gemspec +34 -0
  60. metadata +269 -0
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ ALERT = '1481553823153'.freeze
6
+ ALERT_BODY = {
7
+ name: 'SDK test alert',
8
+ target: 'user@example.com',
9
+ condition: 'ts("app.errors") > 0',
10
+ displayExpression: 'ts("app.errors")',
11
+ minutes: 5,
12
+ resolveAfterMinutes: 5,
13
+ severity: 'INFO'
14
+ }.freeze
15
+
16
+ # Unit tests for Alert class
17
+ #
18
+ class WavefrontAlertTest < WavefrontTestBase
19
+ def test_list
20
+ should_work(:list, 10, '?offset=10&limit=100')
21
+ end
22
+
23
+ def test_describe
24
+ should_work(:describe, ALERT, ALERT)
25
+ assert_raises(ArgumentError) { wf.describe }
26
+ should_be_invalid(:describe)
27
+ end
28
+
29
+ def test_create
30
+ should_work(:create, ALERT_BODY, '', :post, JSON_POST_HEADERS,
31
+ ALERT_BODY.to_json)
32
+ assert_raises(ArgumentError) { wf.create }
33
+ assert_raises(ArgumentError) { wf.create('test') }
34
+ end
35
+
36
+ def test_describe_v
37
+ should_work(:describe, [ALERT, 4], "#{ALERT}/history/4")
38
+ end
39
+
40
+ def test_delete
41
+ should_work(:delete, ALERT, ALERT, :delete)
42
+ should_be_invalid(:delete)
43
+ end
44
+
45
+ def test_history
46
+ should_work(:history, ALERT, "#{ALERT}/history")
47
+ should_be_invalid(:history)
48
+ end
49
+
50
+ def test_snooze
51
+ should_work(:snooze, ALERT, "#{ALERT}/snooze", :post, POST_HEADERS)
52
+ should_work(:snooze, [ALERT, 3600], "#{ALERT}/snooze?seconds=3600",
53
+ :post, POST_HEADERS)
54
+ should_be_invalid(:snooze)
55
+ end
56
+
57
+ def test_update
58
+ should_work(:update, [ALERT, ALERT_BODY], ALERT, :put,
59
+ JSON_POST_HEADERS, ALERT_BODY.to_json)
60
+ should_be_invalid(:update, ['abcde', ALERT_BODY])
61
+ assert_raises(ArgumentError) { wf.update }
62
+ end
63
+
64
+ def test_tags
65
+ tag_tester(ALERT)
66
+ end
67
+
68
+ def test_undelete
69
+ should_work(:undelete, ALERT, ["#{ALERT}/undelete", nil], :post,
70
+ POST_HEADERS)
71
+ should_be_invalid(:undelete)
72
+ end
73
+
74
+ def test_unsnooze
75
+ should_work(:unsnooze, ALERT, ["#{ALERT}/unsnooze", nil], :post,
76
+ POST_HEADERS)
77
+ should_be_invalid(:unsnooze)
78
+ end
79
+
80
+ def test_summary
81
+ should_work(:summary, nil, 'summary')
82
+ end
83
+ end
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../spec_helper'
4
+ require_relative '../../lib/wavefront-sdk/base'
5
+
6
+ #
7
+ # Test SDK base class
8
+ #
9
+ class WavefrontBaseTest < MiniTest::Test
10
+ attr_reader :wf, :wf_noop, :uri_base, :headers
11
+
12
+ def setup
13
+ @wf = Wavefront::Base.new(CREDS)
14
+ @wf_noop = Wavefront::Base.new(CREDS, noop: true)
15
+ @uri_base = "https://#{CREDS[:endpoint]}/api/v2/base"
16
+ @headers = { 'Authorization' => "Bearer #{CREDS[:token]}" }
17
+ @update_keys = []
18
+ end
19
+
20
+ def test_time_to_ms
21
+ now_ms = Time.now.to_i * 1000
22
+ assert_equal wf.time_to_ms(now_ms), now_ms
23
+ assert_equal wf.time_to_ms(1469711187), 1469711187000
24
+ refute wf.time_to_ms([])
25
+ refute wf.time_to_ms('1469711187')
26
+ end
27
+
28
+ def test_uri_concat
29
+ assert_equal %w(a b).uri_concat, 'a/b'
30
+ assert_equal ['', 'a', 'b'].uri_concat, '/a/b'
31
+ assert_equal %w(a /b).uri_concat, 'a/b'
32
+ assert_equal ['', 'a', 'b/'].uri_concat, '/a/b'
33
+ assert_equal %w(/a /b/ /c).uri_concat, '/a/b/c'
34
+ assert_equal ['/a', '/b c'].uri_concat, '/a/b c'
35
+ end
36
+
37
+ def test_api_get
38
+ uri = "#{uri_base}/path?key1=val1"
39
+ stub_request(:get, uri).to_return(body: DUMMY_RESPONSE, status: 200)
40
+ wf.api_get('/path', key1: 'val1')
41
+ assert_requested(:get, uri, headers: headers)
42
+ end
43
+
44
+ def test_api_post
45
+ uri = "#{uri_base}/path"
46
+ obj = {key: 'value'}
47
+ stub_request(:post, uri).to_return(body: DUMMY_RESPONSE, status: 200)
48
+ wf.api_post('/path', 'string')
49
+ assert_requested(:post, uri, body: 'string',
50
+ headers: headers.merge(POST_HEADERS))
51
+ wf.api_post('/path', obj)
52
+ assert_requested(:post, uri, body: obj.to_json,
53
+ headers: headers.merge(POST_HEADERS))
54
+ end
55
+
56
+ def test_api_put
57
+ uri = "#{uri_base}/path"
58
+ stub_request(:put, uri).to_return(body: DUMMY_RESPONSE, status: 200)
59
+ wf.api_put('/path', 'body')
60
+ assert_requested(:put, uri, body: 'body'.to_json,
61
+ headers: headers.merge(JSON_POST_HEADERS))
62
+ end
63
+
64
+ def test_api_delete
65
+ uri = "#{uri_base}/path"
66
+ stub_request(:delete, uri).to_return(body: DUMMY_RESPONSE, status: 200)
67
+ wf.api_delete('/path')
68
+ assert_requested(:delete, uri, headers: headers)
69
+ end
70
+
71
+ def test_api_noop
72
+ uri = "#{uri_base}/path"
73
+
74
+ %w(get post put delete).each do |call|
75
+ stub_request(call.to_sym, uri)
76
+ wf_noop.send("api_#{call}", '/path')
77
+ refute_requested(call.to_sym, uri)
78
+ end
79
+ end
80
+
81
+ def test_hash_for_update
82
+ wf.instance_variable_set('@update_keys', [:k1, :k2])
83
+ body = { k1: 'ov1', k2: 'ov2', k3: 'ov3'}
84
+ upd = { k2: 'nv1' }
85
+
86
+ assert_equal(wf.hash_for_update(body, upd), { k1: 'ov1', k2: 'nv1'})
87
+ end
88
+ end
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ CLOUD = '3b56f61d-1a79-46f6-905c-d75a0f613d10'.freeze
6
+ CLOUD_BODY = {
7
+ name: 'SDK test Cloudwatch Integration',
8
+ service: 'CLOUDWATCH',
9
+ cloudWatch: {
10
+ baseCredentials: {
11
+ roleArn: 'arn:aws:iam::<accountid>:role/<rolename>',
12
+ externalId: 'wave123'
13
+ }
14
+ },
15
+ metricFilterRegex: '^aws.(sqs|ec2|ebs|elb).*$'
16
+ }.freeze
17
+
18
+ # Unit tests for CloudIntegration class
19
+ #
20
+ class WavefrontCloudIntegrationTest < WavefrontTestBase
21
+ def test_list
22
+ should_work(:list, 10, '?offset=10&limit=100')
23
+ end
24
+
25
+ def test_create
26
+ should_work(:create, CLOUD_BODY, '', :post, JSON_POST_HEADERS,
27
+ CLOUD_BODY.to_json)
28
+ assert_raises(ArgumentError) { wf.create }
29
+ assert_raises(ArgumentError) { wf.create('test') }
30
+ end
31
+
32
+ def test_delete
33
+ should_work(:delete, CLOUD, CLOUD, :delete)
34
+ should_be_invalid(:delete)
35
+ end
36
+
37
+ def test_describe
38
+ should_work(:describe, CLOUD, CLOUD)
39
+ should_be_invalid(:describe)
40
+ end
41
+
42
+ def test_update
43
+ should_work(:update, [CLOUD, CLOUD_BODY], CLOUD, :put,
44
+ JSON_POST_HEADERS, CLOUD_BODY.to_json)
45
+ should_be_invalid(:update, ['abcde', CLOUD_BODY])
46
+ assert_raises(ArgumentError) { wf.update }
47
+ end
48
+
49
+ def test_undelete
50
+ should_work(:undelete, CLOUD, ["#{CLOUD}/undelete", nil], :post,
51
+ POST_HEADERS)
52
+ should_be_invalid(:undelete)
53
+ end
54
+ end
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ require_relative '../spec_helper'
5
+ require_relative '../../lib/wavefront-sdk/credentials'
6
+
7
+ CONF = Pathname.new(__FILE__).dirname.realpath + 'resources' + 'test.conf'
8
+
9
+ # Test SDK base class
10
+ #
11
+ class WavefrontCredentialsTest < MiniTest::Test
12
+ attr_reader :wf, :wf_noop, :uri_base, :headers
13
+
14
+ def test_initialize_1
15
+ ENV.delete('WAVEFRONT_ENDPOINT')
16
+ ENV.delete('WAVEFRONT_TOKEN')
17
+ c = Wavefront::Credentials.new(file: CONF)
18
+ assert_instance_of(Wavefront::Credentials, c)
19
+ assert_instance_of(Hash, c.creds)
20
+ assert_instance_of(Hash, c.proxy)
21
+ assert_instance_of(Hash, c.config)
22
+
23
+ assert_equal(c.creds.keys, [:token, :endpoint])
24
+ assert_equal(c.creds[:token], '12345678-abcd-1234-abcd-123456789012')
25
+ assert_equal(c.creds[:endpoint], 'default.wavefront.com')
26
+ end
27
+
28
+ def test_initialize_env_token
29
+ ENV.delete('WAVEFRONT_ENDPOINT')
30
+ ENV['WAVEFRONT_TOKEN'] = 'abcdefgh'
31
+ c = Wavefront::Credentials.new(file: CONF)
32
+ assert_instance_of(Wavefront::Credentials, c)
33
+ assert_instance_of(Hash, c.creds)
34
+ assert_instance_of(Hash, c.proxy)
35
+ assert_instance_of(Hash, c.config)
36
+
37
+ assert_equal(c.creds.keys, [:token, :endpoint])
38
+ assert_equal(c.creds[:token], 'abcdefgh')
39
+ assert_equal(c.creds[:endpoint], 'default.wavefront.com')
40
+ end
41
+
42
+ def test_initialize_env_endpoint
43
+ ENV.delete('WAVEFRONT_TOKEN')
44
+ ENV['WAVEFRONT_ENDPOINT'] = 'endpoint.wavefront.com'
45
+ c = Wavefront::Credentials.new(file: CONF)
46
+ assert_instance_of(Wavefront::Credentials, c)
47
+ assert_instance_of(Hash, c.creds)
48
+ assert_instance_of(Hash, c.proxy)
49
+ assert_instance_of(Hash, c.config)
50
+
51
+ assert_equal(c.creds.keys, [:token, :endpoint])
52
+ assert_equal(c.creds[:token], '12345678-abcd-1234-abcd-123456789012')
53
+ assert_equal(c.creds[:endpoint], 'endpoint.wavefront.com')
54
+ end
55
+ end
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ DASHBOARD = 'test_dashboard'.freeze
6
+ DASHBOARD_BODY = {
7
+ name: 'SDK Dashboard test',
8
+ id: 'sdk-test',
9
+ description: 'dummy test dashboard',
10
+ sections: [
11
+ name: 'Section 1',
12
+ rows: [
13
+ { charts: [
14
+ name: 'S1 Chart1',
15
+ description: 'chart',
16
+ sources: [
17
+ { name: 'S1 C1 Source 1',
18
+ query: 'ts("some.series")' }
19
+ ]
20
+ ] }
21
+ ]
22
+ ]
23
+ }.freeze
24
+
25
+ # Unit tests for dashboard class
26
+ #
27
+ class WavefrontDashboardTest < WavefrontTestBase
28
+ def test_list
29
+ should_work(:list, 10, '?offset=10&limit=100')
30
+ end
31
+
32
+ def test_create
33
+ should_work(:create, DASHBOARD_BODY, '', :post,
34
+ JSON_POST_HEADERS, DASHBOARD_BODY.to_json)
35
+ assert_raises(ArgumentError) { wf.create }
36
+ assert_raises(ArgumentError) { wf.create('test') }
37
+ end
38
+
39
+ def test_describe
40
+ should_work(:describe, DASHBOARD, DASHBOARD)
41
+ assert_raises(ArgumentError) { wf.describe }
42
+ end
43
+
44
+ def test_describe_v
45
+ should_work(:describe, [DASHBOARD, 4], "#{DASHBOARD}/history/4")
46
+ end
47
+
48
+ def test_delete
49
+ should_work(:delete, DASHBOARD, DASHBOARD, :delete)
50
+ should_be_invalid(:delete)
51
+ end
52
+
53
+ def test_history
54
+ should_work(:history, DASHBOARD, "#{DASHBOARD}/history")
55
+ should_be_invalid(:history)
56
+ end
57
+
58
+ def test_update
59
+ should_work(:update, [DASHBOARD, DASHBOARD_BODY], DASHBOARD, :put,
60
+ JSON_POST_HEADERS, DASHBOARD_BODY.to_json)
61
+ should_be_invalid(:update, ['!invalid dash!', DASHBOARD_BODY])
62
+ assert_raises(ArgumentError) { wf.update }
63
+ end
64
+
65
+ def test_tags
66
+ tag_tester(DASHBOARD)
67
+ end
68
+
69
+ def test_undelete
70
+ should_work(:undelete, DASHBOARD, ["#{DASHBOARD}/undelete",
71
+ nil], :post, POST_HEADERS)
72
+ should_be_invalid(:undelete)
73
+ end
74
+ end
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ EVENT = '1481553823153:testev'.freeze
6
+ EVENT_BODY = {
7
+ name: 'test_event',
8
+ annotations: {
9
+ severity: 'info',
10
+ type: 'SDK test event',
11
+ details: 'an imaginary event to test the SDK'
12
+ },
13
+ hosts: %w(host1 host2),
14
+ startTime: 1493385089000,
15
+ endTime: 1493385345678,
16
+ tags: %w(tag1 tag2),
17
+ isEphemeral: false
18
+ }.freeze
19
+
20
+ # Unit tests for event class
21
+ #
22
+ class WavefrontEventTest < WavefrontTestBase
23
+ def test_list
24
+
25
+ t1 = Time.now - 600
26
+ t2 = Time.now
27
+ tms1 = t1.to_datetime.strftime('%Q')
28
+ tms2 = t2.to_datetime.strftime('%Q')
29
+
30
+ assert_raises(ArgumentError) { wf.list }
31
+ assert_raises(ArgumentError) { wf.list(tms1) }
32
+
33
+ should_work(:list, [t1, t2],
34
+ "?earliestStartTimeEpochMillis=#{tms1}" \
35
+ "&latestStartTimeEpochMillis=#{tms2}" \
36
+ '&limit=100')
37
+
38
+ should_work(:list, [tms1, tms2],
39
+ "?earliestStartTimeEpochMillis=#{tms1}" \
40
+ "&latestStartTimeEpochMillis=#{tms2}" \
41
+ '&limit=100')
42
+
43
+ assert_raises(Wavefront::Exception::InvalidTimestamp) do
44
+ wf.list(t1, 'abc')
45
+ end
46
+ end
47
+
48
+ def test_create
49
+ should_work(:create, EVENT_BODY, '', :post,
50
+ JSON_POST_HEADERS, EVENT_BODY.to_json)
51
+ assert_raises(ArgumentError) { wf.create }
52
+ assert_raises(ArgumentError) { wf.create('test') }
53
+ end
54
+
55
+ def test_describe
56
+ should_work(:describe, EVENT, EVENT)
57
+ should_be_invalid(:describe, 'abcdefg')
58
+ assert_raises(ArgumentError) { wf.describe }
59
+ end
60
+
61
+ def test_close
62
+ should_work(:close, EVENT, "#{EVENT}/close", :post, POST_HEADERS)
63
+ should_be_invalid(:close, 'abcdefg')
64
+ assert_raises(ArgumentError) { wf.close }
65
+ end
66
+
67
+ def test_update
68
+ should_work(:update, [EVENT, EVENT_BODY, false], EVENT, :put,
69
+ JSON_POST_HEADERS, EVENT_BODY.to_json)
70
+ #should_be_invalid(:update, ['abcde', EVENT_BODY])
71
+ #assert_raises(ArgumentError) { wf.update }
72
+ end
73
+
74
+ def test_delete
75
+ should_work(:delete, EVENT, EVENT, :delete)
76
+ should_be_invalid(:delete, 'abcdefg')
77
+ assert_raises(ArgumentError) { wf.delete }
78
+ end
79
+
80
+ def test_tags
81
+ tag_tester(EVENT)
82
+ end
83
+ end
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ EXTERNAL_LINK = 'lq6rPlSg2CFMSrg6'.freeze
6
+ EXTERNAL_LINK_BODY = {
7
+ name: 'test link',
8
+ template: 'https://example.com/link/{{value}}',
9
+ description: 'an imaginary link for unit testing purposes'
10
+ }
11
+
12
+ EXTERNAL_LINK_BODY_2 = {
13
+ name: 'test link',
14
+ template: 'https://example.com/link/{{value}}'
15
+ }
16
+
17
+ # Unit tests for ExternalLink class
18
+ #
19
+ class WavefrontExternalLinkTest < WavefrontTestBase
20
+ def api_base
21
+ 'extlink'
22
+ end
23
+
24
+ def test_list
25
+ should_work(:list, 10, '?offset=10&limit=100')
26
+ end
27
+
28
+ def test_describe
29
+ should_work(:describe, EXTERNAL_LINK, EXTERNAL_LINK)
30
+ should_be_invalid(:describe, 'abcdefg')
31
+ assert_raises(ArgumentError) { wf.describe }
32
+ end
33
+
34
+ def test_create
35
+ should_work(:create, EXTERNAL_LINK_BODY, '', :post,
36
+ JSON_POST_HEADERS, EXTERNAL_LINK_BODY.to_json)
37
+
38
+ should_work(:create, EXTERNAL_LINK_BODY_2, '', :post,
39
+ JSON_POST_HEADERS, EXTERNAL_LINK_BODY_2
40
+ .merge!(description: '').to_json)
41
+
42
+ assert_raises(ArgumentError) { wf.create }
43
+ assert_raises(ArgumentError) { wf.create('test') }
44
+ end
45
+
46
+ def test_delete
47
+ should_work(:delete, EXTERNAL_LINK, EXTERNAL_LINK, :delete)
48
+ should_be_invalid(:delete, 'abcdefg')
49
+ assert_raises(ArgumentError) { wf.delete }
50
+ end
51
+
52
+ def test_update
53
+ should_work(:update, [EXTERNAL_LINK, EXTERNAL_LINK_BODY],
54
+ EXTERNAL_LINK, :put, JSON_POST_HEADERS,
55
+ EXTERNAL_LINK_BODY.to_json)
56
+
57
+ should_work(:update, [EXTERNAL_LINK, EXTERNAL_LINK_BODY_2],
58
+ EXTERNAL_LINK, :put, JSON_POST_HEADERS,
59
+ EXTERNAL_LINK_BODY_2.to_json)
60
+
61
+ should_be_invalid(:update, ['abcde', EXTERNAL_LINK_BODY])
62
+ assert_raises(ArgumentError) { wf.update }
63
+ assert_raises(ArgumentError) { wf.update(EXTERNAL_LINK) }
64
+ end
65
+ end