teuton 2.10.8 → 2.11.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.
- checksums.yaml +4 -4
- data/README.md +65 -18
- data/docs/commands/README.md +18 -81
- data/docs/commands/check.md +66 -0
- data/docs/commands/config.md +120 -0
- data/docs/commands/help.md +37 -0
- data/docs/commands/new.md +34 -0
- data/docs/commands/readme.md +52 -0
- data/docs/commands/{run-tests.md → run.md} +1 -1
- data/docs/config_file.md +125 -0
- data/docs/devel/README.md +12 -0
- data/docs/dsl/expect.md +2 -2
- data/docs/dsl/run.md +30 -67
- data/docs/install/s-node.md +9 -7
- data/docs/install/t-node.md +18 -19
- data/docs/learn/11-export.md +1 -1
- data/docs/learn/13-feedback.md +1 -1
- data/docs/learn/14-moodle_id.md +4 -4
- data/docs/learn/15-readme.md +14 -8
- data/docs/learn/16-include.md +21 -16
- data/docs/learn/17-alias.md +2 -2
- data/docs/learn/18-log.md +5 -3
- data/docs/learn/19-read_vars.md +7 -3
- data/docs/learn/20-macros.md +6 -4
- data/docs/learn/21-exit_codes.md +3 -3
- data/docs/learn/22-result.md +7 -3
- data/docs/learn/23-test-code.md +17 -7
- data/docs/learn/24-test-sql.md +19 -18
- data/docs/learn/25-expect-result.md +1 -0
- data/docs/learn/26-expect_sequence.md +12 -7
- data/docs/learn/27-run_script.md +11 -5
- data/docs/tutorial/es/nginx/README.md +546 -0
- data/lib/teuton/case/case.rb +0 -2
- data/lib/teuton/case/dsl/expect.rb +2 -2
- data/lib/teuton/case/dsl/log.rb +1 -1
- data/lib/teuton/case/dsl/run.rb +2 -3
- data/lib/teuton/case/dsl/upload.rb +1 -1
- data/lib/teuton/case/execute/execute_base.rb +3 -6
- data/lib/teuton/case/execute/execute_local.rb +5 -2
- data/lib/teuton/case/execute/execute_ssh.rb +0 -1
- data/lib/teuton/case/execute/execute_telnet.rb +0 -2
- data/lib/teuton/case/play.rb +2 -2
- data/lib/teuton/case/result/result.rb +1 -3
- data/lib/teuton/case_manager/case_manager.rb +14 -17
- data/lib/teuton/case_manager/dsl.rb +4 -4
- data/lib/teuton/case_manager/export_manager.rb +22 -17
- data/lib/teuton/case_manager/{check_cases.rb → ext/check_cases.rb} +7 -6
- data/lib/teuton/case_manager/ext/hall_of_fame.rb +28 -0
- data/lib/teuton/case_manager/{report.rb → ext/report.rb} +6 -8
- data/lib/teuton/case_manager/send_manager.rb +1 -0
- data/lib/teuton/case_manager/{show_report.rb → show_resume_report.rb} +12 -26
- data/lib/teuton/case_manager/stats_manager.rb +26 -0
- data/lib/teuton/check/checker.rb +16 -8
- data/lib/teuton/check/main.rb +2 -2
- data/lib/teuton/check/show.rb +3 -3
- data/lib/teuton/cli.rb +3 -2
- data/lib/teuton/readme/dsl/getset.rb +1 -0
- data/lib/teuton/readme/main.rb +2 -2
- data/lib/teuton/readme/readme.rb +2 -2
- data/lib/teuton/report/formatter/default/array.rb +1 -3
- data/lib/teuton/report/formatter/default/markdown.rb +1 -1
- data/lib/teuton/report/formatter/default/txt.rb +1 -1
- data/lib/teuton/report/formatter/default/xml.rb +93 -29
- data/lib/teuton/report/formatter/formatter.rb +2 -1
- data/lib/teuton/report/formatter/resume/array.rb +1 -3
- data/lib/teuton/report/formatter/resume/txt.rb +6 -2
- data/lib/teuton/report/formatter/resume/xml.rb +92 -0
- data/lib/teuton/report/report.rb +1 -1
- data/lib/teuton/utils/config_file_reader.rb +141 -0
- data/lib/teuton/utils/name_file_finder.rb +12 -15
- data/lib/teuton/utils/project.rb +19 -21
- data/lib/teuton/utils/settings.rb +3 -1
- data/lib/teuton/version.rb +1 -1
- data/lib/teuton.rb +11 -10
- metadata +35 -30
- data/docs/commands/check-example.md +0 -53
- data/docs/es/aprender/01-cmd_new.md +0 -27
- data/docs/es/aprender/02-target.md +0 -131
- data/docs/es/aprender/README.md +0 -36
- data/docs/ideas/todo.md +0 -44
- data/docs/install/README.md +0 -38
- data/lib/teuton/case_manager/hall_of_fame.rb +0 -29
- data/lib/teuton/files/README.md +0 -9
- data/lib/teuton/utils/configfile_reader.rb +0 -135
- /data/docs/{ideas → devel}/contributions.md +0 -0
- /data/docs/{ideas → es}/Challenge-Server-Project.md +0 -0
- /data/docs/{ideas → es}/servidor-de-retos.md +0 -0
- /data/docs/{install/modes_of_use.md → modes_of_use.md} +0 -0
- /data/lib/teuton/{case_manager → deprecated}/utils.rb +0 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
[<< back](../../../README.md)
|
|
2
|
+
|
|
3
|
+
# Tutorial: Nginx Web Server
|
|
4
|
+
|
|
5
|
+
Vamos a hacer un tutorial de cómo crear un test para checkear la instalación de un servidor web Nginx.
|
|
6
|
+
|
|
7
|
+
Esquema de clase:
|
|
8
|
+
```mermaid
|
|
9
|
+
graph TD
|
|
10
|
+
%% Definición de los nodos
|
|
11
|
+
P["profesor(teuton)"]
|
|
12
|
+
R(( Network ))
|
|
13
|
+
A1["alumno1(ssh)"]
|
|
14
|
+
A2["alumno2(ssh)"]
|
|
15
|
+
An["alumnoN(ssh)"]
|
|
16
|
+
|
|
17
|
+
%% Conexiones
|
|
18
|
+
P --- R
|
|
19
|
+
R --- A1
|
|
20
|
+
R --- A2
|
|
21
|
+
R --- An
|
|
22
|
+
|
|
23
|
+
%% Estilos (Opcional)
|
|
24
|
+
style P fill:#aaffaa,stroke:#006400,stroke-width:2px,color:#000
|
|
25
|
+
style R fill:#fff,stroke:#333,stroke-dasharray: 5 5
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 1. Consultamos a la IA generativa
|
|
29
|
+
|
|
30
|
+
_¿Cómo no? En estos tiempos todo el mundo está hablando de la IA generativa. Así que vamos a usarla para que nos ayude con el test de Teuton? ;-)_
|
|
31
|
+
|
|
32
|
+
* Abro Gemini y le pongo el siguiente prompt:
|
|
33
|
+
```
|
|
34
|
+
Usando la siguiente plantilla de ejemplo de la herramienta Teuton:
|
|
35
|
+
|
|
36
|
+
group "Learn about targets" do
|
|
37
|
+
target "Create user obiwan", weight: 2
|
|
38
|
+
run "id obiwan"
|
|
39
|
+
expect ["uid=", "(obiwan)", "gid="]
|
|
40
|
+
|
|
41
|
+
target "Delete user vader"
|
|
42
|
+
run "id vader"
|
|
43
|
+
expect_fail
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
start do
|
|
47
|
+
show
|
|
48
|
+
export
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
quiero que hagas un test para evaluar que tenemos activo el servicio web nginx
|
|
52
|
+
y que la página principal index.html tiene en su contenido el texto "Hola Mundo!"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
* La respuesta de Gemini es un buen inicio:
|
|
56
|
+
```ruby
|
|
57
|
+
group "Nginx Web Service Checks" do
|
|
58
|
+
|
|
59
|
+
# Objetivo 1: Verificar que el servicio Nginx está activo
|
|
60
|
+
# Se asume que el comando 'systemctl status' está disponible (en sistemas Linux basados en systemd)
|
|
61
|
+
target "Check Nginx service status", weight: 3
|
|
62
|
+
|
|
63
|
+
# Comando para verificar el estado del servicio
|
|
64
|
+
run "systemctl status nginx"
|
|
65
|
+
|
|
66
|
+
# Expectativa: La salida del comando debe contener el texto "Active: active (running)"
|
|
67
|
+
# para confirmar que el servicio está en ejecución.
|
|
68
|
+
expect ["Active: active (running)"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Objetivo 2: Verificar el contenido de la página principal (index.html)
|
|
72
|
+
# Se asume que la página index.html está en el directorio raíz de Nginx (/var/www/html/ o similar)
|
|
73
|
+
target "Check index.html content for 'Hola Mundo!'", weight: 5
|
|
74
|
+
|
|
75
|
+
# Comando para leer el contenido del archivo de la página principal
|
|
76
|
+
# NOTA: Ajusta la ruta del archivo si tu configuración Nginx es diferente.
|
|
77
|
+
run "cat /var/www/html/index.html"
|
|
78
|
+
|
|
79
|
+
# Expectativa: El contenido del archivo debe incluir el texto exacto "Hola Mundo!"
|
|
80
|
+
expect ["Hola Mundo!"]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
start do
|
|
84
|
+
show
|
|
85
|
+
export
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 2. Refinamos manualmente
|
|
90
|
+
|
|
91
|
+
Vamos a realizar algunos cambios manualmente:
|
|
92
|
+
* Vamos a poner los textos en español.
|
|
93
|
+
* Los textos de los comentarios que contengan información útil para el alumno, nos puede interesar incluirlos en el enunciado de la práctica. Para ello usamos la instrucción `readme`.
|
|
94
|
+
* El resto de los comentarios, me parecen evidentes, los elimino.
|
|
95
|
+
* En la instrucción `expect` podemos quitar los corchetes si sólo tenemos un filtro. Así, es más fácil de leer.
|
|
96
|
+
* El `readme` después del `group`es una aclaración de información del grupo, y el `readme` después del `target` es una aclaración de ese target concreto.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
group "Comprobar el servicio web Nginx" do
|
|
100
|
+
readme "Necesitamos un SO GNU/Linux basado en systemd. Por ejemplo: OpenSUSE, Debian, etc"
|
|
101
|
+
|
|
102
|
+
target "Comprobar el estado del servicio Nginx", weight: 3
|
|
103
|
+
run "systemctl status nginx"
|
|
104
|
+
expect "Active: active (running)"
|
|
105
|
+
|
|
106
|
+
target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 5
|
|
107
|
+
readme "Se asume que Nginx está instalado en su ruta por defecto"
|
|
108
|
+
run "cat /var/www/html/index.html"
|
|
109
|
+
expect "Hola Mundo!"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
start do
|
|
113
|
+
show
|
|
114
|
+
export
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> **NOTA**: Mientras estamos haciendo cambios en el test podemos usar los siguientes comandos para comprobar que todo va funcionando correctamente.
|
|
119
|
+
> * `teuton readme PATH/TO/FOLDER` para ver cómo se genera el enunciado asociado a la práctica.
|
|
120
|
+
> * `teutob check PATH/TO/FOLDER` para comprobar que no hay fallos en la sintaxis, etc.
|
|
121
|
+
|
|
122
|
+
## 3. Añadimos un fichero de configuración
|
|
123
|
+
|
|
124
|
+
* Para tener mayor legibilidad en el futuro, el primer refinamiento que vamos a realizar es separar los tests específicos de Nginx de las instrucciones del script principal.
|
|
125
|
+
1. `start.rb`: Script principal
|
|
126
|
+
2. `nginx.rb`: Tests específicos de Nginx
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# File: start.rb (Script principal)
|
|
130
|
+
use "nginx"
|
|
131
|
+
|
|
132
|
+
start do
|
|
133
|
+
show
|
|
134
|
+
export
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# File: nginx.rb (Tests específicos de Nginx)
|
|
141
|
+
|
|
142
|
+
group "Comprobar el servicio web Nginx" do
|
|
143
|
+
readme "* Necesitamos un SO GNU/Linux basado en systemd. Por ejemplo: OpenSUSE, Debian, etc."
|
|
144
|
+
|
|
145
|
+
target "Comprobar el estado del servicio Nginx", weight: 3
|
|
146
|
+
run "systemctl status nginx"
|
|
147
|
+
expect "Active: active (running)"
|
|
148
|
+
|
|
149
|
+
target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 5
|
|
150
|
+
readme "Se asume que Nginx está instalado en su ruta por defecto."
|
|
151
|
+
run "cat /var/www/html/index.html"
|
|
152
|
+
expect "Hola Mundo!"
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Ahora mismo, el test se ejecuta directamente en la máquina `localhost`, pero vamos a modificarlo para que se pueda ejecutar en las máquinas remotas de nuestros alumnos.
|
|
157
|
+
* Modificamos la instrucción `run` para indicar dónde se tiene que ejecutar el comando: `run COMMAND, on: :webserver`. El nombre de host `webserver` es completamente arbitrario. Lo ideal es poner algo significativo para nosotros como: `nginx` `server`, `host1`, `linux`, etc. Cualquiera valdría.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# File: nginx.rb (Tests específicos de Nginx)
|
|
161
|
+
|
|
162
|
+
group "Comprobar el servicio web Nginx" do
|
|
163
|
+
readme "* Necesitamos un SO GNU/Linux basado en systemd. Por ejemplo: OpenSUSE, Debian, etc."
|
|
164
|
+
|
|
165
|
+
target "Comprobar el estado del servicio Nginx", weight: 3
|
|
166
|
+
run "systemctl status nginx", on: :webserver
|
|
167
|
+
expect "Active: active (running)"
|
|
168
|
+
|
|
169
|
+
target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 5
|
|
170
|
+
readme "Se asume que Nginx está instalado en su ruta por defecto."
|
|
171
|
+
run "cat /var/www/html/index.html", on: :webserver
|
|
172
|
+
expect "Hola Mundo!"
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Tenemos que incluir un fichero de configuración para especificar la configuración específica de cada una de las máquinas de nuestros alumnos, que son las que queremos evaluar realmente.
|
|
177
|
+
|
|
178
|
+
* A partir del contenido del test, Teuton es capaz de deducir los parámetros que se necesitan para su ejecución. Para ello usamos el comando `teuton config PATH/FOLDER`.
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
---
|
|
182
|
+
global:
|
|
183
|
+
cases:
|
|
184
|
+
- tt_members: TOCHANGE
|
|
185
|
+
webserver_ip: TOCHANGE
|
|
186
|
+
webserver_username: TOCHANGE
|
|
187
|
+
webserver_password: TOCHANGE
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
* Personalizamos los valores de los parámetros:
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
---
|
|
194
|
+
global:
|
|
195
|
+
cases:
|
|
196
|
+
# Máquina del alumno 1
|
|
197
|
+
- tt_members: Alumno 1
|
|
198
|
+
webserver_ip: 192.168.122.254
|
|
199
|
+
webserver_username: user
|
|
200
|
+
webserver_password: secret
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 4. Ejecutamos el test sobre las máquinas remotas
|
|
204
|
+
|
|
205
|
+
* Ahora vamos a ejecutar el test `teuton run PATH/TO/FOLDER`.
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
------------------------------------
|
|
209
|
+
Started at 2025-12-05 23:10:38 +0000
|
|
210
|
+
FF
|
|
211
|
+
Finished in 0.674 seconds
|
|
212
|
+
------------------------------------
|
|
213
|
+
|
|
214
|
+
CASE RESULTS
|
|
215
|
+
+------+----------+-------+-------+
|
|
216
|
+
| CASE | MEMBERS | GRADE | STATE |
|
|
217
|
+
| 01 | Alumno 1 | 0.0 | ? |
|
|
218
|
+
+------+----------+-------+-------+
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
El alumno 1 tiene una nota final de 0. Esto es porque todavía no ha realizado la práctica.
|
|
222
|
+
|
|
223
|
+
Como profesores, mientras estamos diseñando el test, es muy útil tener una MV que haga de la máquina del alumno para ir haciendo las pruebas durante el proceso. Por motivos didácticos, vamos a añadir una segunda MV (del alumno2), para simular que tenemos un grupo de clase donde tenemos que a un alumno le sale la práctica mal y a otro bien.
|
|
224
|
+
|
|
225
|
+
* Ampliamos el fichero de configuración con 2 cases:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
---
|
|
229
|
+
global:
|
|
230
|
+
cases:
|
|
231
|
+
# Máquina del alumno 1
|
|
232
|
+
- tt_members: Alumno 1
|
|
233
|
+
webserver_ip: 192.168.122.254
|
|
234
|
+
webserver_username: user
|
|
235
|
+
webserver_password: secret
|
|
236
|
+
# Máquina del alumno 2
|
|
237
|
+
- tt_members: Alumno 2
|
|
238
|
+
webserver_ip: 192.168.122.108
|
|
239
|
+
webserver_username: user
|
|
240
|
+
webserver_password: secret
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
> **NOTA**:
|
|
244
|
+
> * Los valores de las IP son los de mis MV ahora pero pueden ser diferentes.
|
|
245
|
+
> * Estoy usando el hipervisor KVM para crear las MV dentro de máquina real, pero se pueden usar otros como VirtualBox, Qemu, Parallel, Hyper-V, incluso contenedores con SSH en ejecución, máquinas reales, Raspberry PI, etc.
|
|
246
|
+
|
|
247
|
+
* Ejecutamos el test. De momento todas las notas están a 0 porque los alumnos todavía no han realizado la práctica:
|
|
248
|
+
```
|
|
249
|
+
$ teuton run nginx/v02
|
|
250
|
+
------------------------------------
|
|
251
|
+
Started at 2025-12-06 16:25:10 +0000
|
|
252
|
+
FFFF
|
|
253
|
+
Finished in 0.601 seconds
|
|
254
|
+
------------------------------------
|
|
255
|
+
|
|
256
|
+
CASE RESULTS
|
|
257
|
+
+------+----------+-------+-------+
|
|
258
|
+
| CASE | MEMBERS | GRADE | STATE |
|
|
259
|
+
| 01 | Alumno 1 | 0.0 | ? |
|
|
260
|
+
| 02 | Alumno 2 | 0.0 | ? |
|
|
261
|
+
+------+----------+-------+-------+
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## 5. Optimizando el fichero de configuración
|
|
265
|
+
|
|
266
|
+
Ahora sólo tenemos dos alumnos (cases) en el fichero de configuración, pero sabemos que este número va a aumentar bastante cuando lo llevemos al aula. También vemos que tenemos unos parámetros con valores repetidos en cada case: `webserver_username: user` y `webserver_password: secret`.
|
|
267
|
+
|
|
268
|
+
Para no repetirnos (_DRY: Don't repeat Yourself_) usamos la sección `global` de la siguiente forma:
|
|
269
|
+
|
|
270
|
+
```yaml
|
|
271
|
+
---
|
|
272
|
+
global:
|
|
273
|
+
webserver_username: user
|
|
274
|
+
webserver_password: secret
|
|
275
|
+
cases:
|
|
276
|
+
# Máquina del alumno 1
|
|
277
|
+
- tt_members: Alumno 1
|
|
278
|
+
webserver_ip: 192.168.122.254
|
|
279
|
+
# Máquina del alumno 2
|
|
280
|
+
- tt_members: Alumno 2
|
|
281
|
+
webserver_ip: 192.168.122.108
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## 6. Empezamos con la práctica
|
|
285
|
+
|
|
286
|
+
Por motivos didácticos, me voy a convertir en el alumno2 y voy a ir haciendo la práctica sólo en su MV para que pueda obtener la máxima puntuación mientras que el alumno1 se quedará en 0 (_Sólo es una simulación_).
|
|
287
|
+
|
|
288
|
+
* Voy a la MV de alumno2.
|
|
289
|
+
* Lo primero que pide el enunciado es instalar el servidor web nginx. Como al alumno2 tiene una SO Debian, haremos como root `apt install nginx`.
|
|
290
|
+
* Ejecutamos los tests para ver el cambio:
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
$ teuton run nginx/v02
|
|
294
|
+
------------------------------------
|
|
295
|
+
Started at 2025-12-06 16:41:11 +0000
|
|
296
|
+
F.FF
|
|
297
|
+
Finished in 0.581 seconds
|
|
298
|
+
------------------------------------
|
|
299
|
+
|
|
300
|
+
CASE RESULTS
|
|
301
|
+
+------+----------+-------+-------+
|
|
302
|
+
| CASE | MEMBERS | GRADE | STATE |
|
|
303
|
+
| 01 | Alumno 1 | 0.0 | ? |
|
|
304
|
+
| 02 | Alumno 2 | 38.0 | ? |
|
|
305
|
+
+------+----------+-------+-------+
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
El alumno2 ha subido un poco la nota, pero tiene un valor extraño de un 38%, o lo que es lo mismo un 3,8. _¿De dónde sale ese valor?_
|
|
309
|
+
|
|
310
|
+
## 7. Puntuaciones y pesos
|
|
311
|
+
|
|
312
|
+
El test ahora mismo tiene los siguientes objetivos (`targets`)
|
|
313
|
+
|
|
314
|
+
| Target | Descripción | Peso |
|
|
315
|
+
| ------ | -------------------------------------------------------- | ---- |
|
|
316
|
+
| 01 | Comprobar el estado del servicio Nginx | 3 |
|
|
317
|
+
| 02 | Comprobar que index.html contiene el texto 'Hola Mundo!' | 5 |
|
|
318
|
+
|
|
319
|
+
Si se completa el 100% de los targets entonces se obtiene un total de 8 puntos (3+5), por lo tanto, si sólo se completan 3 de 8 tenemos un grado de cumplimiento del 38% (3,8).
|
|
320
|
+
```
|
|
321
|
+
Fórmula aplicada : (3/8) * 100 = 37,5
|
|
322
|
+
Redondeando nos queda : 38
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Es muy posible que no gusten los valores actuales de los pesos (`weight`), es normal, de hecho no los hemos puesto nosotros. Fue Gemini, en respuesta a nuestro prompt, el que nos hizo esa sugerencia.
|
|
326
|
+
|
|
327
|
+
* Cambiemos los pesos según nuestro criterio. Si no sabemos que valores poner, entonces no pongas pesos y por defecto todos los targets tendrán el mismo peso (weight: 1). Como profesor, lo normal es darle más peso a los targets que consideramos más importantes, pero esto lo iremos ajustando a medida que dominamos la materia que enseñamos.
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
...
|
|
331
|
+
target "Comprobar el estado del servicio Nginx", weight: 4
|
|
332
|
+
...
|
|
333
|
+
target "Comprobar que index.html contiene el texto 'Hola Mundo!'", weight: 6
|
|
334
|
+
...
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
$ teuton run nginx/v02
|
|
339
|
+
------------------------------------
|
|
340
|
+
Started at 2025-12-06 16:53:14 +0000
|
|
341
|
+
.FFF
|
|
342
|
+
Finished in 0.542 seconds
|
|
343
|
+
------------------------------------
|
|
344
|
+
|
|
345
|
+
CASE RESULTS
|
|
346
|
+
+------+----------+-------+-------+
|
|
347
|
+
| CASE | MEMBERS | GRADE | STATE |
|
|
348
|
+
| 01 | Alumno 1 | 0.0 | ? |
|
|
349
|
+
| 02 | Alumno 2 | 40.0 | ? |
|
|
350
|
+
+------+----------+-------+-------+
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
> Si no sabes que pesos poner, no pongas ninguno, o lo que es lo mismo que todos los targets tengan peso 1.
|
|
354
|
+
|
|
355
|
+
## 8. Continuamos con la práctica
|
|
356
|
+
|
|
357
|
+
Seguimos haciendo la práctica en la MV del alumno2.
|
|
358
|
+
* Ahora toca resolver el segundo target: "Comprobar que index.html contiene el texto 'Hola Mundo!'".
|
|
359
|
+
* Ejecutamos el test:
|
|
360
|
+
```
|
|
361
|
+
$ teuton run nginx/v02
|
|
362
|
+
------------------------------------
|
|
363
|
+
Started at 2025-12-06 16:59:08 +0000
|
|
364
|
+
.F.F
|
|
365
|
+
Finished in 0.642 seconds
|
|
366
|
+
------------------------------------
|
|
367
|
+
|
|
368
|
+
CASE RESULTS
|
|
369
|
+
+------+----------+-------+-------+
|
|
370
|
+
| CASE | MEMBERS | GRADE | STATE |
|
|
371
|
+
| 01 | Alumno 1 | 0.0 | ? |
|
|
372
|
+
| 02 | Alumno 2 | 100.0 | ✔ |
|
|
373
|
+
+------+----------+-------+-------+
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## 9. Vamos a personalizar el test
|
|
377
|
+
|
|
378
|
+
Cuando empezamos con estas prácticas de laboratorio, es común, que el profesor prepare una MV base para los alumnos, y que luego se clone esta MV base para cada alumno. De modo que al empezar todos tienen exactamente el mismo entorno.
|
|
379
|
+
|
|
380
|
+
También podemos partir de una MV base común, pero donde cada alumno debe realizar una serie de personalizaciones para que las MV de cada alumno se diferencien entre sí y prevenir que un alumno presente una MV clonada de un compañero como trabajo propio.
|
|
381
|
+
|
|
382
|
+
De modo que vamos a añadir un poco de personalización a las máquinas. Los alumnos, como parte de la práctica, tendrán que personalizar sus máquinas para que cada uno tenga usuario y passwords diferentes.
|
|
383
|
+
|
|
384
|
+
* Modificamos el fichero de configuración para que los usuarios/passwords de cada MV sean diferentes:
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
---
|
|
388
|
+
global:
|
|
389
|
+
cases:
|
|
390
|
+
# Máquina del alumno 1
|
|
391
|
+
- tt_members: Alumno 1
|
|
392
|
+
webserver_ip: 192.168.122.254
|
|
393
|
+
webserver_username: alumno1
|
|
394
|
+
webserver_password: secret1
|
|
395
|
+
# Máquina del alumno 2
|
|
396
|
+
- tt_members: Alumno 2
|
|
397
|
+
webserver_ip: 192.168.122.108
|
|
398
|
+
webserver_username: alumno2
|
|
399
|
+
webserver_password: secret2
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
* Por motivos didácticos, vamos a quitar todos los pesos, o lo que es lo mismo que todos los targets tengan paso 1.
|
|
403
|
+
* Ampliamos la personalización, modificando el segundo target para que el texto de la página web sea diferente para cada alumno/MV, mostrando algo como "Hola Alumno 1!" para al alumno1 y "Hola Alumno 2" para el alumno2. Para ello cambiamos la instrucción `expect`.
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
target "Comprobar que index.html contiene el texto 'Hola Mundo!'"
|
|
407
|
+
readme "Se asume que Nginx está instalado en su ruta por defecto."
|
|
408
|
+
run "cat /var/www/html/index.html", on: :webserver
|
|
409
|
+
expect "Hola #{get(:tt_members)}!"
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
La instrucción `expect` debe evaluar la presencia de un determinado contenido ("Hola STUDENT_NAME!") en la página web. Pero como el valor de STUDENT_NAME es diferente para cada case (alumno) no lo podemos poner fijo, sino que debemos leerlo de la configuración con la instrucción `get(:tt_members)`. De esta forma se obtiene un valor diferente para cada case (alumno).
|
|
413
|
+
|
|
414
|
+
> **SUGERENCIA**: Cuanta mayor personalización añadamos a nuestros tests, más complicado (pero no imposible) les será a los alumnnos "copiar" el trabajo de otros mediante el clonado de MV.
|
|
415
|
+
|
|
416
|
+
_El test está listo para llevarlo al aula, ahora nos falta completar el fichero de configuración con datos reales de nuestros alumnos._
|
|
417
|
+
|
|
418
|
+
## 10. Completar la configuración con datos reales
|
|
419
|
+
|
|
420
|
+
Para nuestra simulación, mientras diseñábamos el test, creamos la configuración para 2 alumnos ficticios. Pero cuando querramos ejecutar el test en el aula hay que añadir las configuraciones de todos los alumnos.
|
|
421
|
+
|
|
422
|
+
Tenemos varias formas de hacerlo, según el estilo de cada docente:
|
|
423
|
+
* **Lo decide el profesor manualmente**: Una posibilidad es que pongamos la configuración de cada alumno según el criterio del profesor y los alumnos deben adaptar sus MV (IP, username, password) seǵun lo que haya determinado el profesor.
|
|
424
|
+
* **Lo decide el alumno y el profesor lo actualiza manualmente**: Otra opción es que cada alumno le indique al profesor los valores de configuración para que el profesor actualice el fichero de configuración manualmente. Esto es un poco "pesado" si hay muchos alumnos. Una posibilidad es que los alumnos envíen al profesor un YAML con su configuración, profesor los guarda en la carpeta `config.d` y mediante el parámetro `tt_include: config.d`, todos los ficheros en la carpeta se incluirán como parte de la configuración del test.
|
|
425
|
+
* **Lo comunica el alumno en remoteo y se actualiza automáticamente**: Una tercera opción es que el profesor utilice el servicio `teuton config --server PATH/TO/FOLDER`, y de esta forma cada alumno proporciona los valores de su configuración, los cuales se irán guardando automáticamente.
|
|
426
|
+
|
|
427
|
+
Veamos en detalle esta última opción.
|
|
428
|
+
|
|
429
|
+
## 11. FUNCION EXPERIMENTAL: Servidor de configuración
|
|
430
|
+
|
|
431
|
+
* El profesor inicia el servidor de configuración:
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
$ teuton config --server nginx/v04.final
|
|
435
|
+
--------------------------------------------------
|
|
436
|
+
ConfigServer URL: http://192.168.1.28:8080
|
|
437
|
+
|
|
438
|
+
Project path : nginx/v04.final
|
|
439
|
+
Global params (1)
|
|
440
|
+
* tt_include : config.d
|
|
441
|
+
Cases params (4)
|
|
442
|
+
* tt_members
|
|
443
|
+
* webserver_ip
|
|
444
|
+
* webserver_username
|
|
445
|
+
* webserver_password
|
|
446
|
+
--------------------------------------------------
|
|
447
|
+
== Sinatra (v4.2.1) has taken the stage on 8080 for development with backup from Puma
|
|
448
|
+
Puma starting in single mode...
|
|
449
|
+
* Puma version: 7.1.0 ("Neon Witch")
|
|
450
|
+
* Ruby version: ruby 3.2.8 (2025-03-26 revision 13f495dc2c) [x86_64-linux]
|
|
451
|
+
* Min threads: 0
|
|
452
|
+
* Max threads: 5
|
|
453
|
+
* Environment: development
|
|
454
|
+
* PID: 8268
|
|
455
|
+
* Listening on http://0.0.0.0:8080
|
|
456
|
+
Use Ctrl-C to stop
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
* Cada alumno, desde su MV, abre el navegador con URL `http://TEACHER_IP:8080`
|
|
460
|
+
* Cada alumno, rellena el formulario web con sus datos:
|
|
461
|
+
|
|
462
|
+

|
|
463
|
+
|
|
464
|
+
* Cuando todos los alumnos envían sus datos, el profesor pulsa CTRL+C para cerrar el servicio de configuración remota. Vemos que se nos ha creado un subdirectorio dentro de nuestro test (`config.d`) donde se han ido guardando los ficheros con la configuración enviada por cada alumno. Cada envío remoto queda registrado con la IP desde donde se realizó.
|
|
465
|
+
|
|
466
|
+
```
|
|
467
|
+
$ tree nginx/v04.final
|
|
468
|
+
nginx/v04.final
|
|
469
|
+
├── config.d
|
|
470
|
+
│ ├── from_192.168.122.101.yaml
|
|
471
|
+
│ └── from_192.168.122.102.yaml
|
|
472
|
+
├── config.yaml
|
|
473
|
+
├── nginx.rb
|
|
474
|
+
└── start.rb
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
* Además, en el fichero de configuración principal, se crea el parámetro `tt_include: config.d` para indicaar que además del contenido de `config.yaml` debemos incluir como parte de la configuración todos los ficheros del subdirectorio `config.d`.
|
|
478
|
+
|
|
479
|
+
```yaml
|
|
480
|
+
---
|
|
481
|
+
global:
|
|
482
|
+
tt_include: config.d
|
|
483
|
+
cases:
|
|
484
|
+
- tt_members: Alumno 1
|
|
485
|
+
webserver_ip: 192.168.122.254
|
|
486
|
+
webserver_username: alumno1
|
|
487
|
+
webserver_password: secret1
|
|
488
|
+
- tt_members: Alumno 2
|
|
489
|
+
webserver_ip: 192.168.122.108
|
|
490
|
+
webserver_username: alumno2
|
|
491
|
+
webserver_password: secret2
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
_¡Ya tenemos el test y las configuraciones listas para trabajar en el aula!_
|
|
495
|
+
|
|
496
|
+
## 12. Sesión de trabajo en el aula
|
|
497
|
+
|
|
498
|
+
Ya, tenemos listo el test y el fichero de configuración. Nos lo llevamos al aula para usarlo en producción.
|
|
499
|
+
|
|
500
|
+
Ejecutamos el test:
|
|
501
|
+
* **Al comienzo de la sesión**: Ejecutamos el test simplemente para validar que hay conectividad con todas las MV de los alumnos aunque en este momento todas las notas estén a 0.
|
|
502
|
+
* **Durante la sesión**: De forma opcional, podemos ir ejecutando el test para ir monitorizando cómo van avanzando los alumnos.
|
|
503
|
+
* **Al final de la sesión**: Al finalizar la sesión ejecutamos el test por última vez para quedarnos con el resultado final.
|
|
504
|
+
|
|
505
|
+
> Cada vez que se ejecuta el test y se guardan los resultados en `var/TESNAME/case-*.txt`, se sobreescriben los ficheros de ejecuciones anteriores.
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## ANEXO: Problemas de conexión
|
|
510
|
+
|
|
511
|
+
Si al ejecutar el test nos encontramos con la siguiente salida:
|
|
512
|
+
```
|
|
513
|
+
$ teuton run nginx/v03.custom
|
|
514
|
+
------------------------------------
|
|
515
|
+
Started at 2025-12-06 20:12:16 +0000
|
|
516
|
+
FFFF
|
|
517
|
+
Finished in 30.033 seconds
|
|
518
|
+
------------------------------------
|
|
519
|
+
|
|
520
|
+
CASE RESULTS
|
|
521
|
+
+------+----------+-------+-------+
|
|
522
|
+
| CASE | MEMBERS | GRADE | STATE |
|
|
523
|
+
| 01 | Alumno 1 | 0.0 | ? |
|
|
524
|
+
| 02 | Alumno 2 | 0.0 | ? |
|
|
525
|
+
+------+----------+-------+-------+
|
|
526
|
+
|
|
527
|
+
CONN ERRORS
|
|
528
|
+
+------+----------+-----------+-------+
|
|
529
|
+
| CASE | MEMBERS | HOST | ERROR |
|
|
530
|
+
| 01 | Alumno 1 | webserver | error |
|
|
531
|
+
| 02 | Alumno 2 | webserver | error |
|
|
532
|
+
+------+----------+-----------+-------+
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Vemos que todas las puntuaciones están a 0, y que aparecen errores de conexión a la máquina `webserver`de todos los alumnos. Para obtener más información sobre el problema de la conexión, podemos consultar el informe de cada alumno. Por ejemplo `var/TESTNAME/case-01.txt`.
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
...
|
|
539
|
+
LOGS
|
|
540
|
+
[20:12:46] ERROR: [Net::SSH::ConnectionTimeout] SSH on <alumno1@192.168.122.254> exec: systemctl status nginx
|
|
541
|
+
...
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Podemos ver cómo en la sección LOGS nos dice que hay un error `SSH::ConnectionTimeout` al intentar acceder vía SSH con el equipo del alumno1.
|
|
545
|
+
|
|
546
|
+
El problema se debe a que los alumnos todavía no han encendido sus MV. Una vez que todos enciendan sus MV prodemos seguir con normalidad.
|
data/lib/teuton/case/case.rb
CHANGED
|
@@ -43,11 +43,9 @@ class Case
|
|
|
43
43
|
@skip = true
|
|
44
44
|
@skip = false if Project.value[:options]["case"].include? @id.to_i
|
|
45
45
|
end
|
|
46
|
-
@debug = Project.debug?
|
|
47
46
|
@verbose = Project.value[:verbose]
|
|
48
47
|
|
|
49
48
|
@tmpdir = File.join("var", @config.get(:tt_testname), "tmp", @id.to_s)
|
|
50
|
-
# ensure_dir @tmpdir # REVISE: When we will need this? Samba?
|
|
51
49
|
|
|
52
50
|
@unique_values = {}
|
|
53
51
|
@result = Result.new
|
|
@@ -15,7 +15,7 @@ module DSL
|
|
|
15
15
|
elsif input.instance_of?(String) || input.instance_of?(Regexp) || input.instance_of?(Array)
|
|
16
16
|
expect_any input
|
|
17
17
|
else
|
|
18
|
-
puts Rainbow("[ERROR] Case expect TypeError: expect #{input} (#{input.class})").red
|
|
18
|
+
puts Rainbow("[ERROR] Case expect TypeError: <expect #{input} (#{input.class})>").red
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -37,7 +37,7 @@ module DSL
|
|
|
37
37
|
@report.lines << @action.clone
|
|
38
38
|
weight(1.0)
|
|
39
39
|
|
|
40
|
-
c = Settings.letter[:
|
|
40
|
+
c = Settings.letter[:fail]
|
|
41
41
|
c = Settings.letter[:good] if cond
|
|
42
42
|
verbose Rainbow(c).green
|
|
43
43
|
end
|
data/lib/teuton/case/dsl/log.rb
CHANGED
data/lib/teuton/case/dsl/run.rb
CHANGED
|
@@ -10,13 +10,12 @@ module DSL
|
|
|
10
10
|
# @param args (Hash)
|
|
11
11
|
def run(command, args = {})
|
|
12
12
|
args[:exec] = command.to_s
|
|
13
|
-
host = :localhost
|
|
14
|
-
host = args[:on] if args[:on]
|
|
13
|
+
host = args[:on] || :localhost
|
|
15
14
|
goto(host, args)
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
# Run command from the host identify as "host"
|
|
19
|
-
# goto :host1, :
|
|
18
|
+
# goto :host1, execute: "command"
|
|
20
19
|
def goto(host = :localhost, args = {})
|
|
21
20
|
@result.reset
|
|
22
21
|
args[:on] = host unless args[:on]
|
|
@@ -28,7 +28,7 @@ module DSL
|
|
|
28
28
|
Net::SFTP.start(
|
|
29
29
|
host.ip, host.username, password: host.password, port: host.port
|
|
30
30
|
) { |sftp| sftp.upload!(localpath, remotepath) }
|
|
31
|
-
verbose(Rainbow(
|
|
31
|
+
verbose(Rainbow(Settings.letter(:upload)).green)
|
|
32
32
|
rescue => e
|
|
33
33
|
log("Upload #{localfile} to #{host.ip}:#{remotepath}", :warn)
|
|
34
34
|
log(e.to_s, :warn)
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
require_relative "../../utils/verbose"
|
|
2
|
-
|
|
3
1
|
class ExecuteBase
|
|
4
|
-
include Verbose
|
|
5
|
-
|
|
6
2
|
def initialize(parent)
|
|
7
3
|
@parent = parent
|
|
8
4
|
# READ: @config, cmd = action[:command]
|
|
@@ -50,8 +46,9 @@ class ExecuteBase
|
|
|
50
46
|
begin
|
|
51
47
|
text = ec.convert(text)
|
|
52
48
|
rescue => e
|
|
53
|
-
puts "[ERROR] #{e}
|
|
54
|
-
puts "
|
|
49
|
+
puts "[ERROR] ExecuteBase: #{e}"
|
|
50
|
+
puts "[ERROR] Declare text encoding. Example:"
|
|
51
|
+
puts "[ERROR] run 'command', on: :host, encoding: 'ISO-8859-1'"
|
|
55
52
|
end
|
|
56
53
|
|
|
57
54
|
text.split("\n").compact
|
|
@@ -5,6 +5,8 @@ require_relative "../../utils/verbose"
|
|
|
5
5
|
require_relative "execute_base"
|
|
6
6
|
|
|
7
7
|
class ExecuteLocal < ExecuteBase
|
|
8
|
+
include Verbose
|
|
9
|
+
|
|
8
10
|
def call
|
|
9
11
|
action[:conn_type] = :local
|
|
10
12
|
response = my_execute(action[:command], action[:encoding])
|
|
@@ -13,15 +15,16 @@ class ExecuteLocal < ExecuteBase
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def my_execute(cmd, encoding = "UTF-8")
|
|
16
|
-
return {exitcode: 0, content: ""} if Project.debug
|
|
18
|
+
return {exitcode: 0, content: ""} if Project.value[:debug]
|
|
17
19
|
|
|
18
20
|
begin
|
|
19
21
|
text, status = Open3.capture2e(cmd)
|
|
20
22
|
exitcode = status.exitstatus
|
|
21
23
|
rescue => e
|
|
22
|
-
verbose Rainbow("!").green
|
|
23
24
|
text = e.to_s
|
|
25
|
+
log("cmd=<#{cmd}> => #{text}", :error)
|
|
24
26
|
exitcode = 1
|
|
27
|
+
verbose Rainbow("!").green
|
|
25
28
|
end
|
|
26
29
|
content = encode_and_split(encoding, text)
|
|
27
30
|
{exitcode: exitcode, content: content}
|