rbeapi 0.1.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 (60) hide show
  1. data/.gitignore +35 -0
  2. data/Gemfile +25 -0
  3. data/Guardfile +15 -0
  4. data/LICENSE +28 -0
  5. data/README.md +218 -0
  6. data/Rakefile +12 -0
  7. data/lib/rbeapi.rb +32 -0
  8. data/lib/rbeapi/api.rb +135 -0
  9. data/lib/rbeapi/api/aaa.rb +410 -0
  10. data/lib/rbeapi/api/dns.rb +198 -0
  11. data/lib/rbeapi/api/interfaces.rb +1193 -0
  12. data/lib/rbeapi/api/ipinterfaces.rb +328 -0
  13. data/lib/rbeapi/api/logging.rb +157 -0
  14. data/lib/rbeapi/api/mlag.rb +519 -0
  15. data/lib/rbeapi/api/ntp.rb +201 -0
  16. data/lib/rbeapi/api/ospf.rb +214 -0
  17. data/lib/rbeapi/api/prefixlists.rb +98 -0
  18. data/lib/rbeapi/api/radius.rb +317 -0
  19. data/lib/rbeapi/api/radius.rb.old +399 -0
  20. data/lib/rbeapi/api/routemaps.rb +100 -0
  21. data/lib/rbeapi/api/snmp.rb +427 -0
  22. data/lib/rbeapi/api/staticroutes.rb +88 -0
  23. data/lib/rbeapi/api/stp.rb +381 -0
  24. data/lib/rbeapi/api/switchports.rb +272 -0
  25. data/lib/rbeapi/api/system.rb +87 -0
  26. data/lib/rbeapi/api/tacacs.rb +236 -0
  27. data/lib/rbeapi/api/varp.rb +181 -0
  28. data/lib/rbeapi/api/vlans.rb +338 -0
  29. data/lib/rbeapi/client.rb +454 -0
  30. data/lib/rbeapi/eapilib.rb +334 -0
  31. data/lib/rbeapi/netdev/snmp.rb +370 -0
  32. data/lib/rbeapi/utils.rb +70 -0
  33. data/lib/rbeapi/version.rb +37 -0
  34. data/rbeapi.gemspec +32 -0
  35. data/spec/fixtures/dut.conf +5 -0
  36. data/spec/spec_helper.rb +22 -0
  37. data/spec/support/fixtures.rb +114 -0
  38. data/spec/support/shared_examples_for_api_modules.rb +124 -0
  39. data/spec/system/api_ospf_interfaces_spec.rb +58 -0
  40. data/spec/system/api_ospf_spec.rb +111 -0
  41. data/spec/system/api_varp_interfaces_spec.rb +60 -0
  42. data/spec/system/api_varp_spec.rb +44 -0
  43. data/spec/system/rbeapi/api/dns_spec.rb +77 -0
  44. data/spec/system/rbeapi/api/interfaces_base_spec.rb +94 -0
  45. data/spec/system/rbeapi/api/interfaces_ethernet_spec.rb +135 -0
  46. data/spec/system/rbeapi/api/interfaces_portchannel_spec.rb +188 -0
  47. data/spec/system/rbeapi/api/interfaces_vxlan_spec.rb +115 -0
  48. data/spec/system/rbeapi/api/ipinterfaces_spec.rb +97 -0
  49. data/spec/system/rbeapi/api/logging_spec.rb +65 -0
  50. data/spec/system/rbeapi/api/mlag_interfaces_spec.rb +80 -0
  51. data/spec/system/rbeapi/api/mlag_spec.rb +94 -0
  52. data/spec/system/rbeapi/api/ntp_spec.rb +76 -0
  53. data/spec/system/rbeapi/api/snmp_spec.rb +68 -0
  54. data/spec/system/rbeapi/api/stp_instances_spec.rb +61 -0
  55. data/spec/system/rbeapi/api/stp_interfaces_spec.rb +71 -0
  56. data/spec/system/rbeapi/api/stp_spec.rb +57 -0
  57. data/spec/system/rbeapi/api/switchports_spec.rb +135 -0
  58. data/spec/system/rbeapi/api/system_spec.rb +38 -0
  59. data/spec/system/rbeapi/api/vlans_spec.rb +121 -0
  60. metadata +274 -0
@@ -0,0 +1,201 @@
1
+ #
2
+ # Copyright (c) 2014, Arista Networks, Inc.
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are
7
+ # met:
8
+ #
9
+ # Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ #
12
+ # Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # Neither the name of Arista Networks nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
24
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30
+ # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+ require 'rbeapi/api'
33
+
34
+ ##
35
+ # Eos is the toplevel namespace for working with Arista EOS nodes
36
+ module Rbeapi
37
+ ##
38
+ # Api is module namesapce for working with the EOS command API
39
+ module Api
40
+
41
+ ##
42
+ # The Ntp class provides an intstance for working with the nodes
43
+ # NTP configuraiton.
44
+ class Ntp < Entity
45
+
46
+ DEFAULT_SRC_INTF = ''
47
+
48
+ ##
49
+ # get returns the nodes current ntp configure as a resource hash
50
+ #
51
+ # @example
52
+ # {
53
+ # source_interface: <string>
54
+ # servers: {
55
+ # prefer: [true, false]
56
+ # }
57
+ # }
58
+ #
59
+ # @return [nil, Hash<Symbol, Object>] Returns the ntp resource as a
60
+ # Hash.
61
+ def get
62
+ response = {}
63
+ response.merge!(parse_source_interface)
64
+ response.merge!(parse_servers)
65
+ response
66
+ end
67
+
68
+ ##
69
+ # parse_source_interface scans the nodes configurations and parses
70
+ # the ntp source interface if configured. If the source interface
71
+ # is not configured, this method will return DEFAULT_SRC_INTF as the
72
+ # value. The return hash is intended to be merged into the resource hash
73
+ #
74
+ # @api private
75
+ #
76
+ # @return [Hash<Symbol, Object>] resource hash attribute
77
+ def parse_source_interface
78
+ mdata = /(?<=^ntp\ssource\s)(.+)$/.match(config)
79
+ { source_interface: mdata.nil? ? DEFAULT_SRC_INTF : mdata[1] }
80
+ end
81
+ private :parse_source_interface
82
+
83
+ ##
84
+ # parse_servers scans the nodes configuration and parses the configured
85
+ # ntp server host names and/or addresses. This method will also return
86
+ # the value of prefer. If no servers are configured, the value will be
87
+ # set to an empty array. The return hash is inteded to be merged into
88
+ # the resource hash
89
+ #
90
+ # @api private
91
+ #
92
+ # @return [Hash<Symbol, Object>] resource hash attribute
93
+ def parse_servers
94
+ servers = config.scan(/(?:ntp server\s)([^\s]+)\s(prefer)?/)
95
+ values = servers.each_with_object({}) do |(srv, prefer), hsh|
96
+ hsh[srv] = { prefer: !prefer.nil? }
97
+ end
98
+ { servers: values }
99
+ end
100
+
101
+ ##
102
+ # set_source_interface configures the ntp source value in the nodes
103
+ # running configuration. If no value is provided in the options, then
104
+ # the ntp source is configured with the no keyword argument. If the
105
+ # default keyword argument is provided and set to true, the value is
106
+ # configured used the default keyword. The default keyword takes
107
+ # precedence over the value keyword if both optiosn are specified.
108
+ #
109
+ # @eos_version 4.13.7M
110
+ #
111
+ # @commands
112
+ # ntp source <value>
113
+ # no ntp source
114
+ # deafult ntp source
115
+ #
116
+ # @param [Hash] :opts Optional keyword arguments
117
+ #
118
+ # @option :opts [String] :value The value to configure the ntp source
119
+ # in the nodes configuration
120
+ #
121
+ # @option :opts [Boolean] :default Configure the ntp source value using
122
+ # the default keyword
123
+ #
124
+ # @return [Boolean] returns true if the command completed successfully
125
+ def set_source_interface(opts = {})
126
+ value = opts[:value]
127
+ default = opts[:default] || false
128
+
129
+ case default
130
+ when true
131
+ cmds = 'default ntp source'
132
+ when false
133
+ cmds = (value ? "ntp source #{value}" : \
134
+ 'no ntp source')
135
+ end
136
+ configure(cmds)
137
+ end
138
+
139
+ ##
140
+ # add_server configures a new ntp server destination hostname or ip
141
+ # address to the list of ntp destinations. The optional prefer argument
142
+ # configures the server as a preferred (true) or not (false) ntp
143
+ # destination.
144
+ #
145
+ # @param [String] :server The IP address or FQDN of the NTP server to
146
+ # be removed from the configuration
147
+ #
148
+ # @param [Boolean] :prefer Appends the prefer keyword argument to the
149
+ # command if this value is true
150
+ #
151
+ # @return [Boolean] returns true if the command completed successfully
152
+ def add_server(server, prefer = false)
153
+ cmd = "ntp server #{server}"
154
+ cmd << ' prefer' if prefer
155
+ configure cmd
156
+ end
157
+
158
+ ##
159
+ # remove_server deletes the provided server destination from the list of
160
+ # ntp server destinations. If the ntp server does not exist in the list
161
+ # of servers, this method will return successful
162
+ #
163
+ # @param [String] :server The IP address or FQDN of the NTP server to
164
+ # be removed from the configuration
165
+ #
166
+ # @return [Boolean] returns true if the command completed successfully
167
+ def remove_server(server)
168
+ configure("no ntp server #{server}")
169
+ end
170
+
171
+ ##
172
+ # set_prefer will set the prefer keyword for the specified ntp server.
173
+ # If the server does not already exist in the configuration, it will be
174
+ # added and the prefer keyword will be set.
175
+ #
176
+ # @eos_version 4.13.7M
177
+ #
178
+ # @commands
179
+ # ntp server <srv> prefer
180
+ # no ntp server <srv> prefer
181
+ #
182
+ # @param [String] :srv The IP address or hostname of the ntp server to
183
+ # configure with the prefer value
184
+ #
185
+ # @param [Boolean] :value The value to configure for prefer. If true
186
+ # the prefer value is configured for the server. If false, then the
187
+ # prefer value is removed.
188
+ #
189
+ # @return [Boolean] returns true if the commands completed successfully
190
+ def set_prefer(srv, value)
191
+ case value
192
+ when true
193
+ cmds = "ntp server #{srv} prefer"
194
+ when false
195
+ cmds = ["no ntp server #{srv} prefer", "ntp server #{srv}"]
196
+ end
197
+ configure cmds
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,214 @@
1
+ #
2
+ # Copyright (c) 2014, Arista Networks, Inc.
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are
7
+ # met:
8
+ #
9
+ # Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ #
12
+ # Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # Neither the name of Arista Networks nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
24
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30
+ # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+ require 'rbeapi/api'
33
+
34
+ module Rbeapi
35
+
36
+ module Api
37
+
38
+ ##
39
+ # The Ospf class is a global class that provides an instance for working
40
+ # with the node's OSPF configuration
41
+ class Ospf < Entity
42
+
43
+ ##
44
+ # Returns the global OSPF configuration from the node
45
+ #
46
+ # @example
47
+ # {
48
+ # "router_id": <string>
49
+ # "areas": {
50
+ # <string>: array<string>
51
+ # },
52
+ # "resdistribute"
53
+ # }
54
+ #
55
+ # @return [Hash] A Ruby hash object that provides the OSPF settings as
56
+ # key / value pairs.
57
+ def get(inst)
58
+ config = get_block("router ospf #{inst}")
59
+ return nil unless config
60
+
61
+ resp = {}
62
+ mdata = /(?<=^\s{3}router-id\s)(.+)$/.match(config)
63
+ resp['router_id'] = mdata.nil? ? '' : mdata[0]
64
+
65
+ mdata = /^\s{3}network\s(.+)\sarea\s(.+)$/.match(config)
66
+ networks = config.scan(/^\s{3}network\s(.+)\sarea\s(.+)$/)
67
+ areas = networks.each_with_object({}) do |cfg, hsh|
68
+ net, area = cfg
69
+ if hsh.include?(area)
70
+ hsh[area] << net
71
+ else
72
+ hsh[area] = [net]
73
+ end
74
+ end
75
+ resp['areas'] = areas
76
+
77
+ values = config.scan(/(?<=^\s{3}redistribute\s)(\w+)[\s|$]*(route-map\s(.+))?/)
78
+
79
+ resp['redistribute'] = values.each_with_object({}) do |value, hsh|
80
+ hsh[value[0]] = { 'route_map' => value[2] }
81
+ end
82
+ resp
83
+ end
84
+
85
+ ##
86
+ # Returns the OSPF configuration from the node as a Ruby hash
87
+ #
88
+ # @example
89
+ # {
90
+ # <pid>: {...}
91
+ # "interfaces": {...}
92
+ # }
93
+ def getall
94
+ response = {}
95
+
96
+ instances = config.scan(/(?<=^router\sospf\s)\d+$/)
97
+ response = instances.each_with_object({}) do |inst, hsh|
98
+ hsh[inst] = get inst
99
+ end
100
+ response['interfaces'] = interfaces.getall
101
+ response
102
+ end
103
+
104
+ def interfaces
105
+ @interfaces if @interfaces
106
+ @interfaces = OspfInterfaces.new(node)
107
+ @interfaces
108
+ end
109
+
110
+ def create(pid)
111
+ configure "router ospf #{pid}"
112
+ end
113
+
114
+ def delete(pid)
115
+ configure "no router ospf #{pid}"
116
+ end
117
+
118
+ def set_router_id(pid, opts = {})
119
+ value = opts[:value]
120
+ default = opts[:default] || false
121
+
122
+ cmds = ["router ospf #{pid}"]
123
+ case default
124
+ when true
125
+ cmds << 'default router-id'
126
+ when false
127
+ cmds << (value ? "router-id #{value}" : 'no router-id')
128
+ end
129
+ configure cmds
130
+ end
131
+
132
+ def add_network(pid, net, area)
133
+ configure ["router ospf #{pid}", "network #{net} area #{area}"]
134
+ end
135
+
136
+ def remove_network(pid, net, area)
137
+ configure ["router ospf #{pid}", "no network #{net} area #{area}"]
138
+ end
139
+
140
+ def set_redistribute(pid, proto, opts = {})
141
+ routemap = opts[:routemap]
142
+ cmds = ["router ospf #{pid}", "redistribute #{proto}"]
143
+ cmds[1] << " route-map #{routemap}" if routemap
144
+ configure cmds
145
+ end
146
+ end
147
+
148
+ class OspfInterfaces < Entity
149
+
150
+ ##
151
+ # Returns a single MLAG interface configuration
152
+ #
153
+ # Example
154
+ # {
155
+ # "name": <string>,
156
+ # "network_type": <string>
157
+ # }
158
+ #
159
+ # @param [String] :name The interface name to return the configuration
160
+ # values for. This must be the full interface identifier.
161
+ #
162
+ # @return [nil, Hash<String, String>] A Ruby hash that represents the
163
+ # MLAG interface confguration. A nil object is returned if the
164
+ # specified interface is not configured
165
+ def get(name)
166
+ config = get_block("interface #{name}")
167
+ return nil unless config
168
+ return nil unless /no switchport$/ =~ config
169
+
170
+ response = {}
171
+ nettype = /ip ospf network point-to-point/ =~ config
172
+ response['network_type'] = nettype.nil? ? 'broadcast' : 'point-to-point'
173
+ response
174
+ end
175
+
176
+ ##
177
+ # Returns the collection of MLAG interfaces as a hash index by the
178
+ # interface name
179
+ #
180
+ # Example
181
+ # {
182
+ # <name>: {...},
183
+ # <name>: {...}
184
+ # }
185
+ #
186
+ # @return [nil, Hash<String, String>] A Ruby hash that represents the
187
+ # MLAG interface confguration. A nil object is returned if no
188
+ # interfaces are configured.
189
+ def getall
190
+ interfaces = config.scan(/(?<=interface\s)[Et|Po|Lo|Vl].+/)
191
+ interfaces.each_with_object({}) do |intf, hsh|
192
+ values = get(intf)
193
+ hsh[intf] = values if values
194
+ end
195
+ end
196
+
197
+ def set_network_type(name, opts = {})
198
+ value = opts[:value]
199
+ default = opts[:default] || false
200
+
201
+ return false unless %w(nil point-to-point).include?(value)
202
+
203
+ cmds = ["interface #{name}"]
204
+ case default
205
+ when true
206
+ cmds << 'default ip ospf network'
207
+ when false
208
+ cmds << (value ? "ip ospf network #{value}" : 'no ip ospf netework')
209
+ end
210
+ configure(cmds)
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,98 @@
1
+ #
2
+ # Copyright (c) 2014, Arista Networks, Inc.
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are
7
+ # met:
8
+ #
9
+ # Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ #
12
+ # Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # Neither the name of Arista Networks nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
24
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30
+ # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+ require 'rbeapi/api'
33
+
34
+ module Rbeapi
35
+
36
+ module Api
37
+
38
+ ##
39
+ # The Prefixlists class provides a configuration instance for working
40
+ # with static routes in EOS.
41
+ #
42
+ class Prefixlists < Entity
43
+
44
+ ##
45
+ # Returns the static routes configured on the node
46
+ #
47
+ # @example
48
+ # {
49
+ # <route>: {
50
+ # "next_hop": <string>,
51
+ # "name": <string, nil>
52
+ # }
53
+ # }
54
+ #
55
+ # @returns [Hash<String, String> The method will return all of the
56
+ # configured static routes on the node as a Ruby hash object. If
57
+ # there are no static routes configured, this method will return
58
+ # an empty hash
59
+
60
+ def get(name)
61
+ config = get_block("ip prefix-list #{name}")
62
+ return nil unless config
63
+
64
+ entries = config.scan(/^\s{3}(?:seq\s)(\d+)\s(permit|deny)\s(.+)$/)
65
+ entries.each_with_object([]) do |entry, arry|
66
+ arry << { 'seq' => entry[0], 'action' => entry[1],
67
+ 'prefex' => entry[2] }
68
+ end
69
+ end
70
+
71
+ def getall
72
+ lists = config.scan(/(?<=^ip\sprefix-list\s).+/)
73
+ lists.each_with_object({}) do |name, hsh|
74
+ values = get name
75
+ hsh[name] = values if values
76
+ end
77
+ end
78
+
79
+ def create(name)
80
+ configure "ip prefix-list #{name}"
81
+ end
82
+
83
+ def add_rule(name, action, prefix, seq = nil)
84
+ cmd = "ip prefix-list #{name}"
85
+ cmd << " seq #{seq}" if seq
86
+ cmd << " #{action} #{prefix}"
87
+ configure cmd
88
+ end
89
+
90
+ def delete(name, seq = nil)
91
+ cmd = "no ip prefix-list #{name}"
92
+ cmd << " seq #{seq}" if seq
93
+ configure cmd
94
+ end
95
+
96
+ end
97
+ end
98
+ end