oxidized 0.30.1 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -2
  3. data/.github/workflows/stale.yml +4 -2
  4. data/.rubocop.yml +18 -2
  5. data/.rubocop_todo.yml +5 -12
  6. data/CHANGELOG.md +61 -1
  7. data/CONTRIBUTING.md +5 -0
  8. data/Dockerfile +82 -21
  9. data/README.md +5 -21
  10. data/Rakefile +3 -2
  11. data/docs/Configuration.md +36 -12
  12. data/docs/Creating-Models.md +45 -4
  13. data/docs/Hooks.md +34 -0
  14. data/docs/Issues.md +91 -0
  15. data/docs/Model-Notes/Cumulus.md +5 -0
  16. data/docs/Model-Notes/FSOS.md +5 -0
  17. data/docs/Model-Notes/FortiOS.md +21 -5
  18. data/docs/Model-Notes/HPEAruba.md +31 -0
  19. data/docs/Model-Notes/OS6.md +10 -0
  20. data/docs/Model-Notes/RouterOS.md +15 -0
  21. data/docs/Model-Notes/SikluMHTG.md +7 -0
  22. data/docs/Outputs.md +2 -0
  23. data/docs/Release.md +18 -15
  24. data/docs/Sources.md +21 -0
  25. data/docs/Supported-OS-Types.md +11 -5
  26. data/docs/Troubleshooting.md +35 -0
  27. data/examples/device-simulation/README.md +173 -0
  28. data/examples/device-simulation/cmdsets/aoscx +9 -0
  29. data/examples/device-simulation/cmdsets/arubainstant +5 -0
  30. data/examples/device-simulation/cmdsets/asa +7 -0
  31. data/examples/device-simulation/cmdsets/ios +7 -0
  32. data/examples/device-simulation/cmdsets/nxos +5 -0
  33. data/examples/device-simulation/cmdsets/routeros +5 -0
  34. data/examples/device-simulation/cmdsets/srosmd +11 -0
  35. data/examples/device-simulation/device2yaml.rb +225 -0
  36. data/examples/device-simulation/yaml/aoscx_R0X25A-6410_FL.10.10.1100.yaml +2281 -0
  37. data/examples/device-simulation/yaml/aoscx_R8N85A-C6000-48G-CL4_PL.10.08.1010.yaml +451 -0
  38. data/examples/device-simulation/yaml/arubainstant_IAP515_8.10.0.6_VWLC.yaml +213 -0
  39. data/examples/device-simulation/yaml/asa_5512_9.12-4-67_single-context.yaml +531 -0
  40. data/examples/device-simulation/yaml/asr920_16.8.1b.yaml +1122 -0
  41. data/examples/device-simulation/yaml/garderos_R7709_003_006_068.yaml +101 -0
  42. data/examples/device-simulation/yaml/iosxe_C9200L-24P-4G_17.09.04a.yaml +514 -0
  43. data/examples/device-simulation/yaml/iosxe_C9800-L-F-K9_17.06.05.yaml +417 -0
  44. data/examples/device-simulation/yaml/riverbed_915.yaml +123 -0
  45. data/examples/device-simulation/yaml/routeros_CHR_7.10.1.yaml +145 -0
  46. data/examples/device-simulation/yaml/routeros_CHR_7.16.yaml +79 -0
  47. data/examples/device-simulation/yaml/routeros_L009UiGS_7.15.2.yaml +353 -0
  48. data/examples/podman-compose/Makefile +60 -17
  49. data/examples/podman-compose/README.md +63 -27
  50. data/examples/podman-compose/docker-compose.yml +11 -2
  51. data/examples/podman-compose/gitserver/.gitignore +1 -0
  52. data/examples/podman-compose/gitserver/Dockerfile +14 -0
  53. data/examples/podman-compose/model-simulation/Dockerfile-model +1 -1
  54. data/examples/podman-compose/model-simulation/asternos.sh +2 -0
  55. data/examples/podman-compose/oxidized-config/.gitignore +2 -0
  56. data/examples/podman-compose/oxidized-config/config +1 -1
  57. data/examples/podman-compose/oxidized-config/config_csv-file +46 -0
  58. data/examples/podman-compose/oxidized-config/config_csv-gitserver +56 -0
  59. data/examples/podman-compose/oxidized-ssh/.gitignore +1 -0
  60. data/lib/oxidized/config.rb +7 -1
  61. data/lib/oxidized/hook/githubrepo.rb +37 -7
  62. data/lib/oxidized/hook/slackdiff.rb +29 -7
  63. data/lib/oxidized/input/http.rb +1 -0
  64. data/lib/oxidized/input/telnet.rb +1 -1
  65. data/lib/oxidized/manager.rb +17 -16
  66. data/lib/oxidized/model/aoscx.rb +16 -2
  67. data/lib/oxidized/model/aosw.rb +7 -1
  68. data/lib/oxidized/model/arubainstant.rb +90 -0
  69. data/lib/oxidized/model/audiocodes.rb +2 -2
  70. data/lib/oxidized/model/cnos.rb +13 -10
  71. data/lib/oxidized/model/cumulus.rb +3 -0
  72. data/lib/oxidized/model/dlink.rb +1 -0
  73. data/lib/oxidized/model/dlinknextgen.rb +3 -0
  74. data/lib/oxidized/model/edgecos.rb +2 -1
  75. data/lib/oxidized/model/eos.rb +2 -0
  76. data/lib/oxidized/model/f5os.rb +17 -0
  77. data/lib/oxidized/model/firewareos.rb +10 -1
  78. data/lib/oxidized/model/fortios.rb +24 -1
  79. data/lib/oxidized/model/garderos.rb +43 -0
  80. data/lib/oxidized/model/h3c.rb +1 -1
  81. data/lib/oxidized/model/ibos.rb +1 -0
  82. data/lib/oxidized/model/ios.rb +20 -12
  83. data/lib/oxidized/model/iosxr.rb +1 -1
  84. data/lib/oxidized/model/lenovonos.rb +2 -0
  85. data/lib/oxidized/model/linuxgeneric.rb +1 -1
  86. data/lib/oxidized/model/netgear.rb +1 -1
  87. data/lib/oxidized/model/nodegrid.rb +1 -1
  88. data/lib/oxidized/model/nsxdfw.rb +30 -0
  89. data/lib/oxidized/model/nxos.rb +2 -1
  90. data/lib/oxidized/model/os6.rb +48 -0
  91. data/lib/oxidized/model/rgos.rb +1 -1
  92. data/lib/oxidized/model/riverbed.rb +104 -0
  93. data/lib/oxidized/model/routeros.rb +2 -2
  94. data/lib/oxidized/model/saos.rb +18 -1
  95. data/lib/oxidized/model/siklumhtg.rb +22 -0
  96. data/lib/oxidized/model/uplinkolt.rb +46 -0
  97. data/lib/oxidized/model/vyatta.rb +2 -2
  98. data/lib/oxidized/model/xos.rb +7 -0
  99. data/lib/oxidized/node.rb +30 -18
  100. data/lib/oxidized/nodes.rb +13 -5
  101. data/lib/oxidized/output/file.rb +45 -42
  102. data/lib/oxidized/output/git.rb +185 -160
  103. data/lib/oxidized/output/gitcrypt.rb +188 -186
  104. data/lib/oxidized/output/http.rb +53 -51
  105. data/lib/oxidized/output/output.rb +6 -4
  106. data/lib/oxidized/source/csv.rb +44 -49
  107. data/lib/oxidized/source/http.rb +63 -81
  108. data/lib/oxidized/source/jsonfile.rb +63 -0
  109. data/lib/oxidized/source/source.rb +43 -18
  110. data/lib/oxidized/source/sql.rb +66 -59
  111. data/lib/oxidized/version.rb +2 -2
  112. data/oxidized.gemspec +22 -16
  113. metadata +111 -15
@@ -0,0 +1,173 @@
1
+ # Device simulation
2
+ Oxidized supports [150+ devices](/docs/Supported-OS-Types.md).
3
+ No developer has access to all of these devices, which makes the task of
4
+ maintaining Oxidized difficult:
5
+
6
+ - issues can't be resolved because the developer has no access to the device.
7
+ - further developments can produce regressions.
8
+
9
+ In order to address this, we can simulate the devices. An example for a
10
+ simulation are the [model unit tests](/spec/model) but one could also simulate
11
+ a device within a ssh server.
12
+
13
+ The simulation of devices is currently focused on ssh-based devices. This may
14
+ be extended to other inputs like telnet or ftp in the future.
15
+
16
+ ## YAML Simulation Data
17
+ The underlying data for the simulation is a [YAML](https://yaml.org/) file in
18
+ which we store all relevant information about the device. The most important
19
+ information is the responses to the commands used in the oxidized models.
20
+
21
+ The YAML simulation files are stored under
22
+ [/examples/device-simulation/yaml/](/examples/device-simulation/yaml/).
23
+
24
+ ### Creating a YAML file with device2yaml.rb
25
+ A device does not only output the ASCII text we can see in the console.
26
+ It adds ANSI-escape code for nice colors, bold and underline, \r and so on.
27
+ These are key factors in prompt issues so they must be represented in the YAML
28
+ file. We use the ruby string format with interpolations like \r \e and so on.
29
+ Another important point is trailing spaces at the end of lines. Some text
30
+ editors automatically remove trailing spaces, so we code them with \x20.
31
+
32
+ Although a YAML file could be written by hand, this is quite a tedious task to
33
+ catch all the extra codes and code them into YAML. This can be
34
+ automated with the ruby script
35
+ [device2yaml.rb](/examples/device-simulation/device2yaml.rb).
36
+
37
+ `device2yaml.rb` needs ruby and the gem
38
+ [net-ssh](https://rubygems.org/gems/net-ssh/) to run. On debian, you can install
39
+ them with `sudo apt install ruby-net-ssh`
40
+
41
+ Run `device2yaml.rb` in the directory `/examples/device-simulation/`, the
42
+ online help tells you the options.
43
+ ```
44
+ device-simulation$ ./device2yaml.rb
45
+ Missing a host to connect to...
46
+
47
+ Usage: device2yaml.rb [user@]host [options]
48
+ -c, --cmdset file Mandatory: specify the commands to be run
49
+ -o, --output file Specify an output YAML-file
50
+ -t, --timeout value Specify the idle timeout beween commands (default: 5 seconds)
51
+ -e, --exec-mode Run ssh in exec mode (without tty)
52
+ -h, --help Print this help
53
+ ```
54
+
55
+ - `[user@]host` specifies the user and host to connect to the device. The
56
+ password will be prompted interactively by the script. If you do not specify a
57
+ user, it will use the user executing the script.
58
+ - You must list the commands you want to run on the device in a file. Just
59
+ enter one command per line. It is important that you enter exactly the commands
60
+ used by the oxidized model, and no abbreviation like `sh run`. Do not forget
61
+ to insert the `post_login` commands at the beginning if the model has some and
62
+ also the `pre_logout`commands at the end.
63
+ Predefined command sets for some models are stored in
64
+ `/examples/device-simulation/cmdsets`.
65
+ - `device2yaml.rb` waits an idle timeout after the last received data before
66
+ sending the next command. The default is 5 seconds. If your device makes a
67
+ longer pause than 5 seconds before or within a command, you will see that the
68
+ output of the command is shortened or slips into the next command in the yaml
69
+ file. You will have to change the idle timeout to a greater value to address
70
+ this.
71
+ - When run without the output argument, `device2yaml.rb` will only print the ssh
72
+ output to the standard output. You must use `-o <model_HW_SW.yaml>` to store the
73
+ collected data in a YAML file.
74
+ - If your oxidized model uses ssh exec mode (look for `exec true` in the model),
75
+ you will have to use the option `-e` to run device2yaml in ssh exec mode.
76
+
77
+ Note that `device2yaml.rb` takes some time to run because of the idle
78
+ timeout of (default) 5 seconds between each command. You can press the "Escape"
79
+ key if you know there is no more data to come for the current command (when you
80
+ see the prompt for the next command), and the script will stop waiting and
81
+ directly process the next command.
82
+
83
+ Here are two examples of how to run the script:
84
+ ```shell
85
+ ./device2yaml.rb OX-SW123.sample.domain -c cmdsets/aoscx -o yaml/aoscx_R8N85A-C6000-48G-CL4_PL.10.08.1010.yaml
86
+ ./device2yaml.rb admin@r7 -c cmdsets/routeros -e -o yaml/routeros_CHR_7.10.1.yaml
87
+ ```
88
+
89
+ ### Publishing the YAML simulation file to oxidized
90
+ Publishing the YAML simulation file of your device helps maintain oxidized.
91
+ This task may take some time, and we are very grateful that you take this time
92
+ for the community!
93
+
94
+ You should pay attention to removing or replacing anything you don't want to
95
+ share with the rest of the world, for example:
96
+
97
+ - Passwords
98
+ - IP Adresses
99
+ - Serial numbers
100
+
101
+ You can also shorten the configuration if you want - we don't need 48 times the
102
+ same config for each interface, but it doesn't hurt either.
103
+
104
+ Take your time, this is an important task: after you have
105
+ uploaded your file on github, it may be impossible to remove it. You can use
106
+ search/replace to make consistent and faster changes (change the hostname).
107
+
108
+ You can leave the section `oxidized_output` unchanged, it is only used for
109
+ [model unit tests](/spec/model). You will find an explanation of how to produce
110
+ the `oxidized_output`-section in the README.md there.
111
+
112
+ The YAML simulation file should be stored under
113
+ [/examples/device-simulation/yaml/](/examples/device-simulation/yaml/. It
114
+ should be named so that it can be easily recognized: model, hardware type,
115
+ software version and optionally a description if you need to differentiate two
116
+ YAML files:
117
+
118
+ - #model_#hardware_#software.yaml
119
+ - #model_#hardware_#software_#description.yaml
120
+
121
+ Examples:
122
+
123
+ - garderos_R7709_003_006_068.yaml
124
+ - iosxe_C9200L-24P-4G_17.09.04a.yaml
125
+ - asa_5512_9.12-4-67_single-context.yaml
126
+
127
+ ### Interactive mode
128
+ The `device2yaml.rb` script is a little dumb and needs some help, especially
129
+ when having a device sending its output page by page and requiring you to press
130
+ space for the next page. `device2yaml.rb` does not know how to handle this.
131
+
132
+ While `device2yaml.rb` is running, you can type anything to the keyboard, it
133
+ will be send to the remote device. So you can press space or 'n' to get the
134
+ next page.
135
+
136
+ You can also use this to enter an enable password.
137
+
138
+ If you press the "Esc" key, `device2yaml.rb` will not wait for the idle timeout
139
+ and will process the next command right away.
140
+
141
+ ### YAML Format
142
+ The yaml file has three sections:
143
+ - init_prompt: describing the lines send by the device before we can send a
144
+ command. It usually includes MOTD banners, and must include the first prompt.
145
+ - commands: the commands the oxidized model sends to the network device and the
146
+ expected output.
147
+ - oxidized_output: the expected output of oxidized, so that you can compare it
148
+ to the output generated by the unit test. This is optional and only used for
149
+ unit tests.
150
+
151
+ The outputs are multiline and use YAML block scalars (`|`), with the trailing \n
152
+ removed (`-` after `|`). The outputs include the echo of the given command and
153
+ the next prompt. Escape characters are coded in Ruby style (\n, \r...).
154
+
155
+ Here is a shortened example of a YAML file:
156
+ ```yaml
157
+ ---
158
+ init_prompt: |-
159
+ \e[4m\rLAB-R1234_Garderos#\e[m\x20
160
+ commands:
161
+ show system version: |-
162
+ show system version
163
+ grs-gwuz-armel/003_005_068 (Garderos; 2021-04-30 16:19:35)
164
+ \e[4m\rLAB-R1234_Garderos#\e[m\x20
165
+ # ...
166
+ exit: ""
167
+ oxidized_output: |
168
+ # grs-gwuz-armel/003_005_068 (Garderos; 2021-04-30 16:19:35)
169
+ #\x20
170
+ # ...
171
+ ```
172
+
173
+
@@ -0,0 +1,9 @@
1
+ no page
2
+ show version
3
+ show environment
4
+ show module
5
+ show interface transceiver
6
+ show system | exclude "Up Time" | exclude "CPU" | exclude "Memory" | exclude "Pkts .x" | exclude "Lowest" | exclude "Missed"
7
+ show running-config
8
+ show system
9
+ exit
@@ -0,0 +1,5 @@
1
+ show version
2
+ show activate status
3
+ show aps
4
+ show running-config no-encrypt
5
+ exit
@@ -0,0 +1,7 @@
1
+ enable
2
+ terminal pager 0
3
+ show mode
4
+ show version
5
+ show inventory
6
+ more system:running-config
7
+ exit
@@ -0,0 +1,7 @@
1
+ terminal length 0
2
+ terminal width 0
3
+ show version
4
+ show vtp status
5
+ show inventory
6
+ show running-config
7
+ exit
@@ -0,0 +1,5 @@
1
+ terminal length 0
2
+ show version
3
+ show inventory
4
+ show running-config
5
+ exit
@@ -0,0 +1,5 @@
1
+ /system resource print
2
+ /system package update print
3
+ /system history print without-paging
4
+ /export show-sensitive
5
+ quit
@@ -0,0 +1,11 @@
1
+ environment more false
2
+ show system information
3
+ show card state
4
+ show chassis
5
+ file show bootlog.txt
6
+ admin show configuration debug full-context
7
+ file show config.dbg
8
+ admin show configuration configure | match persistent-indices post-lines 10000
9
+ admin show configuration bof full-context
10
+ admin show configuration configure full-context
11
+ logout
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'net/ssh'
5
+ require 'optparse'
6
+ require 'etc'
7
+ require 'timeout'
8
+
9
+ # This scripts logs in a network device and outputs a yaml file that can be
10
+ # used for model unit tests in spec/model/
11
+
12
+ # This script is quick & dirty - it grew with the time an could be a project
13
+ # for its own. It works, and that should be enough ;-)
14
+
15
+ ################# Methods
16
+ # Runs cmd in the ssh session, either im exec mode or with a tty
17
+ # saves the output to @output
18
+ def ssh_exec(cmd)
19
+ puts "\n### Sending #{cmd}..."
20
+ @output&.puts " #{cmd}: |-"
21
+
22
+ if @exec_mode
23
+ @ssh_output = @ssh.exec! cmd + "\n"
24
+ else
25
+ @ses.send_data cmd + "\n"
26
+ shell_wait
27
+ end
28
+ yaml_output(' ')
29
+ end
30
+
31
+ # Wait for the ssh command to be executed, with an idle timout @idle_timeout
32
+ # Pressing CTRL-C exits the script
33
+ # Pressing ESC termiates the idle timeout
34
+ def shell_wait
35
+ @ssh_output = ''
36
+ # ssh_output gets appended by chanel.on-data (below)
37
+ # We store the current length of @ssh_output in @ssh_output_length
38
+ # if @ssh_output.length is bigger than @ssh_output_length, we got new data
39
+ @ssh_output_length = 0
40
+
41
+ # Keep track of time for idle timeout
42
+ start_time = Time.now
43
+
44
+ # Loop & wait for @idle_timeout seconds after last output
45
+ # 0.1 means that the loop should run at least once per 0.1 second
46
+ @ssh.loop(0.1) do
47
+ # if @ssh_output is longer than our saved length, we got new output
48
+ if @ssh_output_length < @ssh_output.length
49
+ # reset the timer and save the new output length
50
+ start_time = Time.now
51
+ @ssh_output_length = @ssh_output.length
52
+ end
53
+
54
+ # We wait for 0.1 seconds if a key was pressed
55
+ begin
56
+ Timeout.timeout(0.1) do
57
+ # Get input // this is a blocking call
58
+ char = $stdin.getch
59
+ # If ctrl-c is pressed, exit the script
60
+ if char == "\u0003"
61
+ puts '### CTRL-C pressed, exiting'
62
+ cleanup
63
+ exit
64
+ end
65
+ # If escape is pressed, terminate idle timeout
66
+ if char == "\e"
67
+ puts "\n### ESC pressed, skipping idle timeout"
68
+ return false
69
+ else
70
+ # if not, send the char through ssh
71
+ @ses.send_data char
72
+ end
73
+ end
74
+ rescue Timeout::Error
75
+ # No key pressed
76
+ end
77
+
78
+ # exit the loop when the @idle_timeout has been reached (false = exit)
79
+ Time.now - start_time < @idle_timeout
80
+ end
81
+ end
82
+
83
+ def yaml_output(prepend = '')
84
+ # Now print the collected output to @output
85
+ firstline = true
86
+
87
+ # as we want to prepend 'prepend' to each line, we need each_line and chomp
88
+ # chomp removes the trainling \n
89
+ @ssh_output.each_line(chomp: true) do |line|
90
+ # encode line and remove the first and the trailing double quote
91
+ line = line.dump[1..-2]
92
+ if firstline
93
+ # Make sure the leading space of the first line (if present)
94
+ # is coded with \0x20 or YAML block scalars won't work
95
+ line.sub!(/^\A /, '\x20')
96
+ firstline = false
97
+ end
98
+ # Make sure trailing white spaces are coded with \0x20
99
+ line.gsub!(/ $/, '\x20')
100
+ # prepend white spaces for the yaml block scalar
101
+ line = prepend + line
102
+ @output&.puts line
103
+ end
104
+ end
105
+
106
+ def cleanup
107
+ (@ssh.close rescue true) unless @ssh.closed?
108
+ @output&.close
109
+ end
110
+
111
+ ################# Main loop
112
+
113
+ # Define options
114
+ options = {}
115
+ optparse = OptionParser.new do |opts|
116
+ opts.banner = "Usage: device2yaml.rb [user@]host [options]"
117
+
118
+ opts.on('-c', '--cmdset file', 'Mandatory: specify the commands to be run') do |file|
119
+ options[:cmdset] = file
120
+ end
121
+ opts.on('-o', '--output file', 'Specify an output YAML-file') do |file|
122
+ options[:output] = file
123
+ end
124
+ opts.on('-t', '--timeout value', Integer, 'Specify the idle timeout beween commands (default: 5 seconds)') do |timeout|
125
+ options[:timeout] = timeout
126
+ end
127
+ opts.on('-e', '--exec-mode', 'Run ssh in exec mode (without tty)') { @exec_mode = true }
128
+ opts.on '-h', '--help', 'Print this help' do
129
+ puts opts
130
+ exit
131
+ end
132
+ end
133
+
134
+ # Catch and parse the first argument
135
+ if ARGV[0] && ARGV[0][0] != '-'
136
+ argument = ARGV.shift
137
+ if argument.include?('@')
138
+ ssh_user, ssh_host = argument.split('@')
139
+ else
140
+ ssh_user = Etc.getlogin
141
+ ssh_host = argument
142
+ end
143
+ else
144
+ puts 'Missing a host to connect to...'
145
+ puts
146
+ puts optparse
147
+ exit 1
148
+ end
149
+
150
+ # Parse the options
151
+ optparse.parse!
152
+
153
+ # Get the commands to be run against ssh_host
154
+ unless options[:cmdset]
155
+ puts 'Missing a command set, use option -c'
156
+ puts
157
+ puts optparse
158
+ exit 1
159
+ end
160
+ # make an array of commands to send, ignore empty lines
161
+ ssh_commands = File.read(options[:cmdset]).split(/\n+|\r+/)
162
+
163
+ # Defaut idle timeout: 5 seconds, as tests showed that 2 seconds is too short
164
+ @idle_timeout = options[:timeout] || 5
165
+
166
+ # We will use safe navifation (&.) to call the methods on @output only
167
+ # if @output is not nil
168
+ @output = options[:output] ? File.open(options[:output], 'w') : nil
169
+
170
+ @ssh = Net::SSH.start(ssh_host,
171
+ ssh_user,
172
+ { timeout: 10,
173
+ append_all_supported_algorithms: true })
174
+
175
+ @ssh_output = ''
176
+
177
+ unless @exec_mode
178
+ @ses = @ssh.open_channel do |ch|
179
+ ch.on_data do |_ch, data|
180
+ @ssh_output += data
181
+ # Output the data to stdout for interactive control
182
+ # remove ANSI escape codes, as they can produce problems
183
+ # The code will be printed as '\e[123m' in the output
184
+ print data.gsub("\e", '\e')
185
+ end
186
+ ch.request_pty(term: 'vt100') do |_ch, success_pty|
187
+ raise "Can't get PTY" unless success_pty
188
+
189
+ ch.send_channel_request 'shell' do |_ch, success_shell|
190
+ raise "Can't get shell" unless success_shell
191
+ end
192
+ end
193
+ ch.on_extended_data do |_ch, _type, data|
194
+ $stderr.print "Error: #{data}\n"
195
+ end
196
+ end
197
+ end
198
+
199
+ # YAML begin of file
200
+ @output&.puts '---'
201
+
202
+ if @exec_mode
203
+ # init prompt does not exist and is empty in exec mode
204
+ @output&.puts 'init_prompt:'
205
+ else
206
+ # get motd and first prompt
207
+ @output&.puts 'init_prompt: |-'
208
+ shell_wait
209
+ yaml_output ' '
210
+ end
211
+
212
+ @output&.puts "commands:"
213
+
214
+ begin
215
+ ssh_commands.each do |cmd|
216
+ ssh_exec cmd
217
+ end
218
+ rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError => e
219
+ puts "### Connection closed with message: #{e.message}"
220
+ end
221
+
222
+ @output&.puts 'oxidized_output: |'
223
+ @output&.puts ' !! needs to be written by hand or copy & paste from model output'
224
+
225
+ cleanup