unimatrix-cli 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/unimatrix_cli.rb +11 -29
  4. data/lib/unimatrix_cli/command.rb +0 -1
  5. data/lib/unimatrix_cli/config/acceptance.yml +2 -2
  6. data/lib/unimatrix_cli/config/configuration.rb +1 -1
  7. data/lib/unimatrix_cli/config/production.yml +2 -2
  8. data/lib/unimatrix_cli/config/staging.yml +2 -2
  9. data/lib/unimatrix_cli/iris/stream_converter/create_command.rb +48 -0
  10. data/lib/unimatrix_cli/iris/stream_converter/describe_command.rb +37 -0
  11. data/lib/unimatrix_cli/keymaker/policy/destroy_command.rb +69 -0
  12. data/lib/unimatrix_cli/keymaker/policy/write_command.rb +156 -0
  13. data/lib/unimatrix_cli/keymaker/resource/list_command.rb +37 -0
  14. data/lib/unimatrix_cli/keymaker/resource_owner/find_command.rb +76 -0
  15. data/lib/unimatrix_cli/keymaker/resource_owner/invite_command.rb +60 -0
  16. data/lib/unimatrix_cli/keymaker/resource_owner/policies_command.rb +66 -0
  17. data/lib/unimatrix_cli/keymaker/resource_owner/remove_command.rb +92 -0
  18. data/lib/unimatrix_cli/keymaker/resource_server/list_command.rb +32 -0
  19. data/lib/unimatrix_cli/login_command.rb +1 -1
  20. data/lib/unimatrix_cli/version.rb +1 -1
  21. data/unimatrix-cli.gemspec +1 -1
  22. metadata +15 -31
  23. data/lib/unimatrix_cli/citadel/app/build_command.rb +0 -28
  24. data/lib/unimatrix_cli/citadel/app/console_command.rb +0 -17
  25. data/lib/unimatrix_cli/citadel/app/db_setup_command.rb +0 -17
  26. data/lib/unimatrix_cli/citadel/app/environment_command.rb +0 -30
  27. data/lib/unimatrix_cli/citadel/app/logs_command.rb +0 -17
  28. data/lib/unimatrix_cli/citadel/app/migrate_command.rb +0 -17
  29. data/lib/unimatrix_cli/citadel/app/migrate_status_command.rb +0 -17
  30. data/lib/unimatrix_cli/citadel/app/rake_command.rb +0 -18
  31. data/lib/unimatrix_cli/citadel/app/routes_command.rb +0 -17
  32. data/lib/unimatrix_cli/citadel/citadel_command.rb +0 -285
  33. data/lib/unimatrix_cli/citadel/instance/details_command.rb +0 -18
  34. data/lib/unimatrix_cli/citadel/instance/scp_command.rb +0 -48
  35. data/lib/unimatrix_cli/citadel/instance/ssh_command.rb +0 -29
  36. data/lib/unimatrix_cli/citadel/instance/status_command.rb +0 -17
  37. data/lib/unimatrix_cli/citadel/instance/user_data_command.rb +0 -17
  38. data/lib/unimatrix_cli/citadel/instance/user_data_logs_command.rb +0 -17
  39. data/lib/unimatrix_cli/zephyrus/input/create_command.rb +0 -40
  40. data/lib/unimatrix_cli/zephyrus/input/describe_command.rb +0 -35
  41. data/lib/unimatrix_cli/zephyrus/input/list_command.rb +0 -38
  42. data/lib/unimatrix_cli/zephyrus/output/list_command.rb +0 -44
  43. data/lib/unimatrix_cli/zephyrus/recording/configuration_command.rb +0 -43
  44. data/lib/unimatrix_cli/zephyrus/rendition/list_command.rb +0 -40
  45. data/lib/unimatrix_cli/zephyrus/routing/configuration_command.rb +0 -43
  46. data/lib/unimatrix_cli/zephyrus/transcoding/configuration_command.rb +0 -55
  47. data/lib/unimatrix_cli/zephyrus/transcribing/configuration_command.rb +0 -55
  48. data/lib/unimatrix_cli/zephyrus/transmutation/configuration_command.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 86f06aee20a499466a63b5f39f896dff36c4e8b5
4
- data.tar.gz: 227e1e436d35c9fab6c1cb4712c190a1deba541b
3
+ metadata.gz: 24d21f9666e976ff330fee3407b05cefd04177cf
4
+ data.tar.gz: 9ca1edf54bb314807b953c753dbdfd07e196cbb7
5
5
  SHA512:
6
- metadata.gz: 58cf112a2c172d7f07b5a42d74d56745fd5172e3812ff67ded73cd8ee801a0a0af98ce1bb1ca6f477c30a79055424688d6190f0c53f77043550b161d824f153a
7
- data.tar.gz: be51fa45a46b90782a90512aa470404a542ab8059a8ad8972c3211af46536e40c0a88942147ee482e3e1163359afb8bb09e82bceb06e0c0b440ca41dce69edd0
6
+ metadata.gz: 2533479750b3afd59b7598bdbb71c69d07dbdaf498baf2a64d3125d8607e264f439173e451888d8115a0ce1a651edf33b1d70d9d1fbf4153bcdf61c66da74ef7
7
+ data.tar.gz: 2293bedfea542581e3219a7a4eac82c7ae6e75add82d27a765c1829ffeb42e6ba85c10c292ca51c0bebffdf77c5eab3d67324b183b3f0f8523a3ed5d58f58ad6
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.3.0
1
+ 2.4.0
@@ -28,27 +28,11 @@ require 'unimatrix_cli/alchemist/video_encoder/list_command'
28
28
  require 'unimatrix_cli/cartographer/region/import_cities_command'
29
29
  require 'unimatrix_cli/cartographer/region/create_command'
30
30
 
31
- # citadel
32
- require 'unimatrix_cli/citadel/citadel_command'
33
- require 'unimatrix_cli/citadel/app/build_command'
34
- require 'unimatrix_cli/citadel/app/console_command'
35
- require 'unimatrix_cli/citadel/app/db_setup_command'
36
- require 'unimatrix_cli/citadel/app/environment_command'
37
- require 'unimatrix_cli/citadel/app/logs_command'
38
- require 'unimatrix_cli/citadel/app/migrate_command'
39
- require 'unimatrix_cli/citadel/app/migrate_status_command'
40
- require 'unimatrix_cli/citadel/app/rake_command'
41
- require 'unimatrix_cli/citadel/app/routes_command'
42
- require 'unimatrix_cli/citadel/instance/details_command'
43
- require 'unimatrix_cli/citadel/instance/scp_command'
44
- require 'unimatrix_cli/citadel/instance/ssh_command'
45
- require 'unimatrix_cli/citadel/instance/status_command'
46
- require 'unimatrix_cli/citadel/instance/user_data_command'
47
- require 'unimatrix_cli/citadel/instance/user_data_logs_command'
48
-
49
31
  # iris
50
32
  require 'unimatrix_cli/iris/stream/create_command'
51
33
  require 'unimatrix_cli/iris/stream/describe_command'
34
+ require 'unimatrix_cli/iris/stream_converter/create_command'
35
+ require 'unimatrix_cli/iris/stream_converter/describe_command'
52
36
  require 'unimatrix_cli/iris/stream_encoder/create_command'
53
37
  require 'unimatrix_cli/iris/stream_encoder/describe_command'
54
38
  require 'unimatrix_cli/iris/stream_input/create_command'
@@ -62,17 +46,15 @@ require 'unimatrix_cli/iris/stream_transcriber/describe_command'
62
46
  require 'unimatrix_cli/iris/stream_transmutator/create_command'
63
47
  require 'unimatrix_cli/iris/stream_transmutator/describe_command'
64
48
 
65
- # zephyrus
66
- require 'unimatrix_cli/zephyrus/input/create_command'
67
- require 'unimatrix_cli/zephyrus/input/describe_command'
68
- require 'unimatrix_cli/zephyrus/input/list_command'
69
- require 'unimatrix_cli/zephyrus/output/list_command'
70
- require 'unimatrix_cli/zephyrus/recording/configuration_command'
71
- require 'unimatrix_cli/zephyrus/rendition/list_command'
72
- require 'unimatrix_cli/zephyrus/routing/configuration_command'
73
- require 'unimatrix_cli/zephyrus/transcoding/configuration_command'
74
- require 'unimatrix_cli/zephyrus/transcribing/configuration_command'
75
- require 'unimatrix_cli/zephyrus/transmutation/configuration_command'
49
+ # keymaker
50
+ require 'unimatrix_cli/keymaker/policy/destroy_command'
51
+ require 'unimatrix_cli/keymaker/policy/write_command'
52
+ require 'unimatrix_cli/keymaker/resource/list_command'
53
+ require 'unimatrix_cli/keymaker/resource_owner/find_command'
54
+ require 'unimatrix_cli/keymaker/resource_owner/invite_command'
55
+ require 'unimatrix_cli/keymaker/resource_owner/policies_command'
56
+ require 'unimatrix_cli/keymaker/resource_owner/remove_command'
57
+ require 'unimatrix_cli/keymaker/resource_server/list_command'
76
58
 
77
59
  # must be loaded after all commands in order to dynamically enumerate available commands
78
60
  require 'unimatrix_cli/cli'
@@ -122,7 +122,6 @@ module UnimatrixCLI
122
122
  def available_commands
123
123
  commands = Command.descendants
124
124
  commands.delete( UnimatrixCLI::CLI )
125
- commands.delete( UnimatrixCLI::Citadel::CitadelCommand )
126
125
 
127
126
  parsed_commands = commands.map do | command |
128
127
  command_words = command.to_s.split( '::' )
@@ -1,5 +1,6 @@
1
1
  us-east-1:
2
2
  keymaker_url: "http://keymaker.acceptance.boxxspring.com"
3
+ portal_url: "http://portal.acceptance.boxxspring.com"
3
4
  archivist_url: ""
4
5
  rendition_profile_uuids:
5
6
  - "011e6f444a1e8948f246b45c798e5a16"
@@ -7,11 +8,11 @@ us-east-1:
7
8
  - "2dd9cbab22ed92bb9d2ccc9837df9a3c"
8
9
  - "fef49a4734f873246388384ce1c7e200"
9
10
  - "17ed2ae6c2f091c4c7b1753a17a698b7"
10
- zephyrus_url: "http://zephyrus.acceptance.boxxspring.com"
11
11
  unimatrix_api_url: "http://api.acceptance.unimatrix.io"
12
12
 
13
13
  us-west-2:
14
14
  keymaker_url: "http://us-west-2.keymaker.acceptance.boxxspring.net"
15
+ portal_url: "http://portalx.acceptance.boxxspring.com"
15
16
  archivist_url: "http://us-west-2.archivist.acceptance.boxxspring.net"
16
17
  rendition_profile_uuids:
17
18
  - "011e6f444a1e8948f246b45c798e5a16"
@@ -19,5 +20,4 @@ us-west-2:
19
20
  - "2dd9cbab22ed92bb9d2ccc9837df9a3c"
20
21
  - "fef49a4734f873246388384ce1c7e200"
21
22
  - "17ed2ae6c2f091c4c7b1753a17a698b7"
22
- zephyrus_url: "http://zephyrus.acceptance.boxxspring.com"
23
23
  unimatrix_api_url: "http://us-west-2.api.acceptance.unimatrix.io"
@@ -10,7 +10,7 @@ module UnimatrixCLI
10
10
  default_config_path = File.join(
11
11
  File.dirname( __FILE__ ), "./#{ environment }.yml"
12
12
  )
13
- if File.file?( default_config_path )
13
+ if File.file?( default_config_path ) && region.present?
14
14
  default_config_file = YAML.load_file( default_config_path )
15
15
  default_config_file[ region ]
16
16
  else
@@ -1,15 +1,15 @@
1
1
  us-east-1:
2
2
  keymaker_url: "http://keymaker.boxxspring.com"
3
+ portal_url: "http://portal.boxxspring.com"
3
4
  archivist_url: ""
4
5
  rendition_profile_uuids:
5
6
  - ""
6
- zephyrus_url: "http://zephyrus.boxxspring.com"
7
7
  unimatrix_api_url: "http://api.unimatrix.io"
8
8
 
9
9
  us-west-2:
10
10
  keymaker_url: "http://us-west-2.keymaker.boxxspring.net"
11
+ portal_url: "http://portalx.boxxspring.com"
11
12
  archivist_url: "http://us-west-2.archivist.boxxspring.net"
12
13
  rendition_profile_uuids:
13
14
  - ""
14
- zephyrus_url: "http://zephyrus.boxxspring.com"
15
15
  unimatrix_api_url: "http://us-west-2.api.unimatrix.io"
@@ -1,15 +1,15 @@
1
1
  us-east-1:
2
2
  keymaker_url: "http://keymaker.staging.boxxspring.com"
3
+ portal_url: "http://portal.staging.boxxspring.com"
3
4
  archivist_url: ""
4
5
  rendition_profile_uuids:
5
6
  - ""
6
- zephyrus_url: "http://zephyrus.staging.boxxspring.com"
7
7
  unimatrix_api_url: "http://api.staging.unimatrix.io"
8
8
 
9
9
  us-west-2:
10
10
  keymaker_url: "http://us-west-2.keymaker.staging.boxxspring.net"
11
+ portal_url: "http://portalx.staging.boxxspring.com"
11
12
  archivist_url: "http://us-west-2.archivist.staging.boxxspring.net"
12
13
  rendition_profile_uuids:
13
14
  - ""
14
- zephyrus_url: "http://zephyrus.staging.boxxspring.com"
15
15
  unimatrix_api_url: "http://us-west-2.api.staging.unimatrix.io"
@@ -0,0 +1,48 @@
1
+ module UnimatrixCLI
2
+ module Iris
3
+ module StreamConverter
4
+ class CreateCommand < Command
5
+
6
+ option :realm, "The realm uuid", type: :string, required: true
7
+ option :stream, "The stream_converter's stream", type: :string, required: true
8
+ option :url, "The rtmp destination", type: :string, required: true
9
+ option :url_in, "The input mp4 path", type: :string, required: true
10
+ option :loop, "Whether the output should loop", type: :boolean
11
+
12
+ synopsis "Create a new stream_converter"
13
+
14
+ def execute
15
+ stream_converter = create_stream_converter
16
+
17
+ if validate( stream_converter, 'Iris::StreamConverter' )
18
+ write( message: "StreamConverter uuid: #{ stream_converter.uuid }" )
19
+ else
20
+ write(
21
+ message: "Error creating stream_converter: #{ stream_converter.inspect }",
22
+ error: true
23
+ )
24
+ end
25
+ end
26
+
27
+ #----------------------------------------------------------------------------
28
+
29
+ private; def create_stream_converter
30
+ endpoint = "/streams/#{ @options[ :stream ] }/stream_converters"
31
+
32
+ stream_converter = Unimatrix::Iris::StreamConverter.new(
33
+ url: @options[ :url ],
34
+ url_in: @options[ :url_in ],
35
+ state: 'disconnected'
36
+ )
37
+
38
+ stream_converter.loop = @options[ :loop ] if @options[ :loop ]
39
+
40
+ operation(
41
+ endpoint,
42
+ realm_uuid: @options[ :realm ]
43
+ ).write( 'stream_converters', stream_converter ).first rescue nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ module UnimatrixCLI
2
+ module Iris
3
+ module StreamConverter
4
+ class DescribeCommand < Command
5
+
6
+ option :stream_converter, "The stream_converter uuid", type: :string, required: true, short: :c
7
+ option :stream, "The uuid of its stream", type: :string, required: true
8
+
9
+ synopsis "Describe a stream_converter"
10
+
11
+ def execute
12
+ stream_converter = find_stream_converter
13
+
14
+ if validate( stream_converter, 'Iris::StreamConverter' )
15
+ parse_object( stream_converter ).each do | response |
16
+ write( message: response )
17
+ end
18
+ else
19
+ write(
20
+ message: "StreamConverter could not be found: #{ stream_converter.inspect }",
21
+ error: true
22
+ )
23
+ end
24
+ end
25
+
26
+ #----------------------------------------------------------------------------
27
+
28
+ private; def find_stream_converter
29
+ endpoint = "/streams/#{ @options[ :stream ] }/stream_converters/" +
30
+ "#{ @options[ :stream_converter ] }"
31
+
32
+ operation( endpoint ).read.first
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,69 @@
1
+ module UnimatrixCLI
2
+ module Keymaker
3
+ module Policy
4
+ class DestroyCommand < Command
5
+
6
+ option :realm, "Realm UUID for which to remove the policy", \
7
+ type: :string, required: true
8
+
9
+ option :resource_owner_uuid, "UUID for the resource owner for which to remove the policy", \
10
+ type: :string, required: true
11
+
12
+ option :policy, "Specifications of the policy to delete", \
13
+ type: :string, required: true
14
+
15
+ synopsis "Removes a specific resource owner's policy"
16
+
17
+ def execute
18
+ resource_and_server = @options[ :policy ].split( ':' )
19
+
20
+ if resource_and_server.length == 2
21
+ endpoint = "#{ Configuration.default_config[ 'keymaker_url' ] }/policies?" +
22
+ "access_token=#{ Configuration.access_token }"
23
+
24
+ body = {
25
+ policy: {
26
+ resource: resource_string( resource_and_server )
27
+ },
28
+ resource_owner_uuid: @options[ :resource_owner_uuid ]
29
+ }
30
+
31
+ delete_response = make_request( endpoint, 'Delete', body )
32
+
33
+ if delete_response.empty?
34
+ write(
35
+ message: "Successfully removed policy:\n" +
36
+ "Realm UUID: #{ @options[ :realm ] }\n" +
37
+ "Resource Server: #{ resource_and_server[ 0 ] }\n" +
38
+ "Resource: #{ resource_and_server[ 1 ] }\n\n" +
39
+ "Check policies here: #{ Configuration.default_config[ 'keymaker_url' ] }" +
40
+ "/policies?access_token=#{ Configuration.access_token }&" +
41
+ "resource_owner_uuid=#{ @options[ :resource_owner_uuid ] }"
42
+ )
43
+ else
44
+ write(
45
+ message: "\n**Error removing policy**\n" +
46
+ "Realm UUID: #{ @options[ :realm ] }\n" +
47
+ "Resource Server: #{ resource_and_server[ 0 ] }\n" +
48
+ "Resource: #{ resource_and_server[ 1 ] }\n" +
49
+ "Error: #{ delete_response.inspect }\n\n", error: true
50
+ )
51
+ end
52
+ else
53
+ write(
54
+ message: "Error: policy option incorrectly formatted: #{ @options[ :policy ] }",
55
+ error: true
56
+ )
57
+ end
58
+ end
59
+
60
+ #----------------------------------------------------------------------------
61
+
62
+ private; def resource_string( resource_and_server_array )
63
+ "realm/#{ @options[ :realm ] }::#{ resource_and_server_array[ 0 ] }::" +
64
+ "#{ resource_and_server_array[ 1 ] }/*"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,156 @@
1
+ module UnimatrixCLI
2
+ module Keymaker
3
+ module Policy
4
+ class WriteCommand < Command
5
+
6
+ option :realm, "Realm UUID for which to assign the policies", \
7
+ type: :string, required: true
8
+
9
+ option :resource_owner_uuid, "UUID for the resource owner for which to assign the policies", \
10
+ type: :string, required: true
11
+
12
+ option :file, "Path to file describing the policies to assign", \
13
+ type: :string
14
+
15
+ option :policy, "Specifications of single policy to add", type: :string
16
+
17
+ synopsis "Creates or modifies policies for the given resource owner " +
18
+ "for the given realm."
19
+
20
+ def execute
21
+ if ( @options[ :file ] && @options[ :policy ].nil? ) ||
22
+ ( @options[ :file ].nil? && @options[ :policy ] )
23
+
24
+ if @options[ :file ]
25
+ contents = read_file( @options[ :file ] )
26
+
27
+ if contents.respond_to?( :each )
28
+ total_policies = 0
29
+ policies_created = 0
30
+
31
+ contents.each do | resource_server |
32
+ resource_server_name = resource_server[ 'resource_server' ]
33
+
34
+ if resource_server_name && resource_server[ 'resources' ].respond_to?( :each )
35
+ resource_server[ 'resources' ].each do | resource |
36
+ resource_name = resource[ 'code_name' ]
37
+ actions = resource[ 'actions' ]
38
+
39
+ if resource_name && actions && actions.is_a?( Array ) && actions.present?
40
+ total_policies += 1
41
+
42
+ resource_string = "realm/#{ @options[ :realm ] }::" +
43
+ "#{ resource_server_name }::#{ resource_name }/*"
44
+
45
+ write_response = write_policy(
46
+ resource_string,
47
+ actions
48
+ )
49
+
50
+ if !write_response[ 'error' ]
51
+ policies_created += 1
52
+ else
53
+ write(
54
+ message: "\n**Error writing policy**\n" +
55
+ "Resource String: #{ resource_string }\n" +
56
+ "Actions: #{ actions }\n" +
57
+ "Error: #{ write_response.inspect }\n\n", error: true
58
+ )
59
+ end
60
+ else
61
+ write(
62
+ message: "Resources for #{ resource_server_name } empty " +
63
+ "or not correctly formatted", error: true
64
+ )
65
+ end
66
+ end
67
+ else
68
+ write(
69
+ message: "File missing resource server name or is not " +
70
+ "correctly formatted", error: true
71
+ )
72
+ end
73
+ end
74
+
75
+ write(
76
+ message: "Successfully written #{ policies_created } out of " +
77
+ "#{ total_policies } policies.\n\n" +
78
+ "Check policies here: #{ Configuration.default_config[ 'keymaker_url' ] }" +
79
+ "/policies?access_token=#{ Configuration.access_token }&" +
80
+ "resource_owner_uuid=#{ @options[ :resource_owner_uuid ] }"
81
+ )
82
+ else
83
+ write( message: "File empty or not correctly formatted", error: true )
84
+ end
85
+ else
86
+ resource_string, actions = build_from_policy_string( @options[ :policy ] )
87
+
88
+ if resource_string && actions
89
+ write_response = write_policy( resource_string, actions )
90
+
91
+ if !write_response[ 'error' ]
92
+ write(
93
+ message: "Successfully written policy\n" +
94
+ "#{ write_response[ 'resource' ] }\n" +
95
+ "#{ write_response[ 'actions' ] }"
96
+ )
97
+ else
98
+ write(
99
+ message: "\n**Error writing policy**\n" +
100
+ "Resource String: #{ resource_string }\n" +
101
+ "Actions: #{ actions }\n" +
102
+ "Error: #{ write_response.inspect }\n\n", error: true
103
+ )
104
+ end
105
+ else
106
+ write(
107
+ message: "Error: unable to format policy from given input: #{ @options[ :policy ] }",
108
+ error: true
109
+ )
110
+ end
111
+ end
112
+ else
113
+ write(
114
+ message: "Error: either option --file OR --policy must be specified.",
115
+ error: true
116
+ )
117
+ end
118
+ end
119
+
120
+ #----------------------------------------------------------------------------
121
+
122
+ private; def write_policy( resource_string, actions )
123
+ body = {
124
+ resource_owner_uuid: @options[ :resource_owner_uuid ],
125
+ policy: {
126
+ resource: resource_string,
127
+ actions: actions
128
+ }
129
+ }
130
+
131
+ endpoint = "#{ Configuration.default_config[ 'keymaker_url' ] }/policies?" +
132
+ "access_token=#{ Configuration.access_token }"
133
+
134
+ make_request( endpoint, 'Post', body )
135
+ end
136
+
137
+ private; def build_from_policy_string( policy_string )
138
+ results = [ nil, nil ]
139
+
140
+ split_string = policy_string.split( ':' )
141
+
142
+ if split_string.length == 3
143
+ resource_string = "realm/#{ @options[ :realm ] }::" +
144
+ "#{ split_string[ 0 ] }::#{ split_string[ 1 ] }/*"
145
+
146
+ actions = split_string[ 2 ].split( ',' )
147
+
148
+ results = [ resource_string, actions ]
149
+ end
150
+
151
+ results
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end