Tips para programadores y sus sistemas

Cositas que vienen bien cuando uno trabaja en sistemas para hacer nuestros despliegues, en producción, en

Cosas de Git

Git, esa herramienta para desarrolladores que tantos dolores de cabeza nos da y tanto amor la tenemos

Cosas de Git

Como eliminar de nuestro repositorio git un fichero o directorio

Introducción

Un error muy común es que algunas veces, con las prisas olvidamos añadir a nuestro .gitignore algo que no está en el template con el que trabajamos.

No es correcto dejarnos ciertos ficheros o directorios en el repositorio.

Como eliminar un fichero o directorio en el git (repositorio)

git checkout master -- .gitignore
git add .
git commit -m ‘Update .gitignore’ 
git pull
git rm --cached -r nombre_de_fichero_o_directorio
git add .
git commit -m ‘Remove nombre_de_fichero_o_directorio’ 
git push

Esta operación sólo elimina del git (repositorio) el fichero, pero no de la historia, ni de las ramas.

Como eliminar un fichero o doriectori en el git (repositorio) en todas las ramas y en la historia

Alguna vez he visto un repositorio que por error contenía datos sensibles, en alguna parte de la historia.

Como norma general, se está usando un método complejo y que es propenso a errores utilizando git filter-branch pese a que recibimos un aviso a navegantes.

 git filter-branch --tree-filter "rm -f myssh.sh"  --prune-empty HEAD
WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.

Ni se os ocurra. Es el mejor camino para tener que restaurar la copia y luego forzar un push. Todo ello, peligros para la estabilidad de tu repositorio.

Para hacerlo usaremos git-filter-repo , un paquete de python3 que nos hará la vida más fácil con estas cosas, y que es recomendado por el propio git.

Instalación de git-filter-repo

Repositorio de git-filter-repo

python3 -m pip install --user git-filter-repo
Collecting git-filter-repo
  Downloading git_filter_repo-2.29.0-py2.py3-none-any.whl (97 kB)
     |████████████████████████████████| 97 kB 967 kB/s 
Installing collected packages: git-filter-repo
Successfully installed git-filter-repo-2.29.0

Añadimos el comando a nuestro .bashrc o .zshrc su path. En mi caso la instalación se efectuó en :$HOME/.local/bin

export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH

Hacemos un reload de nuestro RC o salimos de la sesión para aplicar los cambios.

reload

Limpieza con git-filter-repo

Ahora, es hora de trabajar con el comando para limpiar nuestro repositorio.

❯ git-filter-repo  --path myssh.sh --invert-paths --force
Parsed 108 commits
New history written in 0.06 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD está ahora en cce4227 Sin especificar
Enumerando objetos: 405, listo.
Contando objetos: 100% (405/405), listo.
Compresión delta usando hasta 12 hilos
Comprimiendo objetos: 100% (232/232), listo.
Escribiendo objetos: 100% (405/405), listo.
Total 405 (delta 232), reusado 299 (delta 157)
Completely finished after 0.18 seconds.

Como veis en la salida, el programa se ha encargado de viajar por la historia de los commits, y realizar la limpieza y cambiar la historia.

Ahora actualizaremos el remoto. Como veremos, los datos de nuestro .git/config han sufrido una actualización (que bueno tener backups si no tenemos o conocemos bien ciertas cosas)

git add .
git commit -m 'Update gitignore and clean'
[master 7d77a36] Update gitignore and clean
 1 file changed, 1 insertion(+), 1 deletion(-)
git push
fatal: No se ha configurado un destino para el push.
Puedes o especificar una URL desde la línea de comandos o configurar un repositorio remoto usando

    git remote add <nombre> <url>

y luego haciendo push al nombre del remoto

    git push <nombre>

oops… no pasa nada. Se trata de reconstruir la parte que se ha eliminado de la configuración relativa al repositorio remoto.

[remote "origin"]
        url = git@gitlab.castris.com:root/nombre_del_repo.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin

Podemos hacerlo editando .git/config o vía comando

git remote add  master git@gitlab.castris.com:root/utilidades.git
git remote add  origin git@gitlab.castris.com:root/utilidades.git
git push -f --set-upstream origin master
Enumerando objetos: 405, listo.
Contando objetos: 100% (405/405), listo.
Compresión delta usando hasta 12 hilos
Comprimiendo objetos: 100% (157/157), listo.
Escribiendo objetos: 100% (405/405), 401.98 KiB | 100.50 MiB/s, listo.
Total 405 (delta 232), reusado 405 (delta 232)
remote: Resolving deltas: 100% (232/232), done.
To gitlab.castris.com:root/utilidades.git
 + 1ba0d77...cd97f4c master -> master (forced update)

Ahora ya podemos estar tranquilos. Hemos borrado los datos sensitivos de nuestro repositorio.

Agradecimientos y enlaces interesantes

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Git

Como forzar un git pull sobre escribiendo los ficheros locales

Introducción

Algunas veces con git, puede pasar que hay mínimos cambios en local que no son importantes, y queremos hacer un git pull para sobre escribir, pero no nos deja (por seguridad)

Cómo forzar un git pull para sobreescribir los ficheros locales

Es importante entender que cualquier cambio en los ficheros locales, se perderá. Todo cambio, con o sin la opción --hard de los commits locales que no hayan sido subidos, se perderán.


Primero, traer todas las ramas del origen

git fetch -all

Después hacer un backup de la rama actual en local

git branch backup-master

Aquí hay dos opciones:

git reset --hard origin/master

O hacerlo en otra rama

git reset --hard origin/<branch_name>

Explicación

git fetch descarga actualizado del remoto sin intentar un merge o un rebase

Después git reset reiniciará la rama master que tú quieres tomar.

Mantener los commits locales pendientes

Es una buena idea muchas veces mantener los cambios locales creando una rama desde el master antes de hacer el reset.

git checkout master
git branch new-branch-to-save-current-commits
git fetch --all
git reset --hard origin/master

Después de esto, todas las confirmaciones (commits) se mantendrán en la nueva rama, new-branch-to-save-current-commits

Como no, en este tipo de acciones es absolutamente necesario, hacer un backup antes de nada.

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Git

Como hacer debug a git para corregir o conocer problemas de conexión

Mode debug en comando git en red

Alguna vez, se te puede quedar un comando git remoto (pull, push, etc) colgado, como si no funcionara ñla conexión remota o hubiera problemas ssh.

Solución

GIT_TRACE_CURL

$ GIT_TRACE_CURL=true  git clone  https://........
...
17:42:56.835334 http.c:717              <= Recv data:  3 39;....L....qE...5.jl..~..=...................S..6.>Z\.!.
17:42:56.835348 http.c:717              <= Recv data: ......Z%...;0019.h.@.l.'....E........0006..003f.Total 157 (d
17:42:56.835355 http.c:717              <= Recv data: elta 32), reused 88 (delta 28), pack-reused 570006..0000
remote: Total 157 (delta 32), reused 88 (delta 28), pack-reused 57
Receiving objects: 100% (157/157), 1.33 MiB | 3.02 MiB/s, done.
Resolving deltas: 100% (48/48), done.
17:42:56.840003 http.c:729              == Info: Connection #0 to host gitlab.xxxx.xxx left intact
Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como esta, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Algunos tips de Laravel

Cosas de Laravel

Crear una Clase Helper para un proyecto Laravel

Introducción

Muchas veces necesitamos ciertas funciones o métodos, para nuestros proyectos, que por su repercusión o repetición, puede ser interesante tenerlos agrupados. Solemos llamarlos Helpers.

Podemos hacerlo de tres formas Crear un fichero de funciones (el más común entre los bloggers de Laravel) con carga mediante autload Creación de una Clase estática (no muy común pero más fina y menos propensa a choques) Creación de una paquete, que sería interesante si tuviéramos muchísimos helpers en distintas clases y que pudieran ser compartido por multitud de nuestros proyectos o de otros programadores Aquí vamos a mostrar los dos primeros.

helpers.php

Crearemos un fichero cuyo lugar y nombre podría ser app/helpers.php con el contenido de abajo.

Crear un archivo helpers.php

Método incorrecto

No se la de veces que lo habré visto, y me parece horrible y alejado de las buenas prácticas.

<?php 

function asString($data)
{
   $json = asJSON($data);

   return wordwrap($json, 76, "\n   ");
}


function asJSON($data)
{
   $json = json_encode($data);
   $json = preg_replace('/(["\]}])([,:])(["\[{])/', '$1$2 $3', $json);

   return $json;
}

Método correcto

Ya que no usamos una clase sino un archivo tipo include que cargaremos mediante un autload, para evitar problemas de duplicidad de nombres con las funciones de PHP, lo correcto es hacerlo como el código de abajo

<?php

if (!function_exists('asString'))
{
    function asString($data)
    {
        $json = asJSON($data);

        return wordwrap($json, 76, "\n   ");
    }
}

if (!function_exists('asJson'))
{
    function asJSON($data)
    {
        $json = json_encode($data);
        $json = preg_replace('/(["\]}])([,:])(["\[{])/', '$1$2 $3', $json);

        return $json;
    }
}

Carga del archivo

Editamos nuestro composer.json en la sección autoload añadiendo o creando la sección files

"autoload": {
     "psr-4": {
         "App\\": "app/",
         "Database\\Factories\\": "database/factories/",
         "Database\\Seeders\\": "database/seeders/"
     },
     "files": [
         "app/helpers.php
     ] 
},

Actualización del autoload de la app

composer dump-autoload

Uso

El uso es sencillo, ya que se le llama como si fuera una función nativa de PHP.

$headerData = [
   'category' => 'develop',
   'unique_args' => [
       'var_1' => 'abc'
   ]
];

$header = asString($headerData);

Crear una clase estática

La creación de una clase estática, nos permite más seguridad, algo más de estandarización y para trabajar en grupo, y el camino a la creación de nuestro propio paquete de Helpers.

Crear el fichero de clase Helper

En mi ejemplo uso el directorio Helpers dentro de App porque tengo más clases de helpers en un proyecto largo app/Helpers/MailHelpers

<?php

namespace App\Helpers;

class MailHelpers {
    public static function asString($data)
    {
        $json = self::asJson($data);

        return wordwrap($json, 76, "\n   ");
    }

    public static function asJson ($data)
    {
        $json = json_encode($data);
        $json = preg_replace('/(["\]}])([,:])(["\[{])/', '$1$2 $3', $json);

        return $json;
    }
}

De esta forma no necesitamos realizar ninguna modificación en nuestro fichero composer.json, ya que la carga se produce conforme al PSR-4, y es simplemente una clase más de tipo estático.

Uso

Este ejemplo es del uso de la clase MailHelpers en una clase Mail.

<?php

namespace App\Mail;

use App\Helpers\MailHelpers;
…

public function build()
{
  $headerData = [
      'category' => 'develop',
      'unique_args' => [
          'var_1' => 'abc'
      ]
  ];

  $header = MailHelpers::asString($headerData);
  ...
  ...

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Mailhog como mailtrap para desarrollos con Laravel

Introducción

Generalmente uso mailtrap.io para comprobar los correos ya que entre otras herramientas tiene HTML Check pero cuando se trata de trabajos en su inicio que no requieren de esto, y prima la portabilidad, prefiero usar Mailhog, un capturador de correo desarrollado en Go.

Instalación en Ubuntu

Descarga y conversión a ejecutable

Revisar siempre la versión disponible, ya que una entrada de blog o wiki puede no estar actualizada

$ wget https://github.com/mailhog/MailHog/releases/download/v1.0.0/MailHog_linux_amd64
$ sudo cp MailHog_linux_amd64 /usr/local/bin/mailhog
$ sudo chmod +x /usr/local/bin/mailhog

Crear un servicio para Mailhog (systemd)

Se hace uso del comando whoami para cargar el servicio para nuestro usuario. Debemos verificar que lo hizo bien.

$ sudo tee /etc/systemd/system/mailhog.service <<EOL
[Unit]
Description=Mailhog
After=network.target
[Service]
User=$(whoami)
ExecStart=/usr/bin/env /usr/local/bin/mailhog > /dev/null 2>&1 &
[Install]
WantedBy=multi-user.target
EOL

Verificar

$ sudo cat  /etc/systemd/system/mailhog.service
[Unit]
Description=Mailhog
After=network.target
[Service]
User=abkrim
ExecStart=/usr/bin/env /usr/local/bin/mailhog > /dev/null 2>&1 &
[Install]
WantedBy=multi-user.target

Activar y habilitar para arranque con el sistema

Es aconsejable siempre que modifiquemos algo en el systemd hacer un reload del demonio.

$ sudo systemctl daemon-reload

Distintas acciones

$ sudo systemctl start mailhog.service
$ sudo systemctl enable mailhog.service
$ sudo systemctl status mailhog.service

Configurar php.ini

En mi caso es para PHP-FPM y multi versión, por lo que necesito añadirlo a cada uno, para el fpm y para el cli.

$ sudo sed -i "s/;sendmail_path.*/sendmail_path='\/usr\/local\/bin\/mailhog sendmail abkrim@nox.test'/" /etc/php/8.0/fpm/php.ini
$ sudo sed -i "s/;sendmail_path.*/sendmail_path='\/usr\/local\/bin\/mailhog sendmail abkrim@nox.test'/" /etc/php/8.0/cli/php.ini
$ sudo sed -i "s/;sendmail_path.*/sendmail_path='\/usr\/local\/bin\/mailhog sendmail abkrim@nox.test'/" /etc/php/7.4/fpm/php.ini
$ sudo sed -i "s/;sendmail_path.*/sendmail_path='\/usr\/local\/bin\/mailhog sendmail abkrim@nox.test'/" /etc/php/7.4/cli/php.ini

Necesito un reload de php-fpm

$ sudo systemctl restart php8.0-fpm.service
$ sudo systemctl restart php7.4-fpm.service

Ver Mailhog en el navegador

http://localhost:8025/

Mailhog Localhost

Configuracion para Laravel

Editamos el fichero .env

El puerto por defecto es 1025 MAIL_FROM_ADDRESS es requerido

MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=abkrim@nox.local
MAIL_FROM_NAME="${APP_NAME}"

Enlace

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Método en TestCase para facilitar los test de usuario logeado

Desarrollo

En los tests mucha sveces necesitamos realziar pruebas como usuario logeado o usuario especifico. Un buen refactor para esta acción repitida es incluirla en la clase TestCase de la cual extendemos test en Laravel.

TestCase

public function login(User $user = null): User
 {
     $user ??= User::factory()->create();

     $this->actingAs($user);

     return $user;
 }

Ahora será más fácil escribir nuestros tests usando simplemente $this->login

     //$this->actingAs(User::factory()->create());

     $this->login()

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Métodos dump en el proceso de Testing con Laravel

Introducción

Una de las herramientas que más me gustan de Laravel es dd(). Una herrmienta que permite un volocado con exit, que por lo general sale bien formateo, y que es muy útil en algunas corcustancias para localizar problemas o comprender mecanismo y estados en alguna parte del código.

En PHPUnit con Laravel tambien disponemos de herramientas para hacer algo parecido en el proceso de testing.

Volcando datos en la construcción de un test

Tenemos tres elementos todo ellos formando parte de la clase TestReponse de Iluminate/Response

dump()

Que vuelca el contenido de la respuesta (response)

dumpHeaders()

Que vuelca solo el contenido de las headers muy útil cuando trabajamos con Api auqne tambien útil en otras areas

dumpSession()

Que vuelca el contenido de la session de la respuesta

Ejemplo

/** @test */
function date_format_is_validate()
{
  $this->login();

  $post = BlogPost::factory()->create();

  $this
      ->post(action([BlogPostAdminController::class, 'update'], $post->slug), [
          'title' => $post->title,
          'author' => $post->author,
          'body' => $post->body,
          'date' => '01/01/2021',
      ])
      ->dumpSession()
      ->assertSessionHasErrors(['date']);
}

dumpSession en Tetstin Laravel

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Asignar múltiples variables a la vez en PHP

Introducción

La limpieza de código para su lectura es algo muy interesante. A veces tenemos métodos que devuelven un array numérico que queremos incorporar a una serie de variables. Podemos hacerlo al mismo tiempo.

[] = array()

En las pruebas de test, por ejemplo queremos evaluar dos usuario, uno con permisos y otro sin permisos.

Nuestra factoría (Laravel), nos permite obtener un array con dos colecciones en un array y podemos asignarlas a dos variables $guest y $admin

/** @test */
function only_admin_users_are_allowed()
{
  [$guest, $admin] = User::factory()
      ->count(2)
      ->sequence(
          ['is_admin' => false],
          ['is_admin' => true],
      )
      ->create();

Similar trabajo tiene la función list() aunque en este caso no me parece tan limpia.

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Configurar Carbon::now() a una fecha para trabajar con tests

Cosas de Laravel

Código de estado HTTP para llamadas API

Códigos de respuesta HTTP

Los códigos de estado de respuesta HTTP indican si se ha completado satisfactoriamente una solicitud HTTP específica. Las respuestas se agrupan en cinco clases:

  1. Respuestas informativas (100–199),
  2. Respuestas satisfactorias (200–299),
  3. Redirecciones (300–399),
  4. Errores de los clientes (400–499),
  5. Errores de los servidores (500–599).

Los códigos de estado se definen en la sección 10 de RFC 2616. Puedes obtener las especificaciones actualizadas en RFC 7231.

Tabla de uso más cotidiano

No estan todos, pero si los que uso yo, que muchas veces no lo hago por que me quede claro, sino porque Laravel lo hace así, y pese a que en algunos casos no estoy de acuerdo, creo que Taylor sabe más.

Código Respuesta Apreciaciones
100 Continue
200 Ok No todo es 200 y es una manía extendida entre programadores no actualizados
201 Created Típica respuesta de un PUT con resultado correcto
202 Accepted Solicitud sin compromiso, es decir no hay respuesta asincrona
301 Moved Permanently La URI se modifico
400 Bad request Posiblemente una mala sintaxis en la llamada a la api
401 Unauthorized Es necesiario autenticarse. Similar a 403 pero indicando que si se puede logear haciendolo debdamente
403 Forbidden El login no es valido para acceder al recurso solicitado
404 Not found El servidor no pudo encontrar el contenido solicitado. El más famoso
422 Unprocessable Entity La petición estaba bien formada pero no se pudo seguir debido a errores de semántica. Usado por laravel para muchas cosas.
429 Too manya requests Exceso de peticiones en un periodo de tiempo. (Throttling)
500 Internal Server Error El servidor ha encontrado una situación que no sabe cómo manejarla
502 Bad Gateway El servidor anda raro
503 Service unavailable El servidor no está listo para manejar la petición. Causas comunes puede ser que el servidor está caído por mantenimiento o está sobrecargado.
Fuente

Códigos de estado de respuesta HTTP

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Testing error SQLSTATE[HY000]: General error: 1 near "ALTER": syntax error (SQL: ALTER TABLE

Introducción

Algunas veces hay que modificar columnas en nuestros desarrollos. Laravel nospermite la creación de migraciones especializadas en este tipo de acciones, pero supeditadas a Doctrine/dbal el cual muchas cosas no las hace

Presta atención a esa peculiaridad sobre todo en el uso de cosas como las columnas enum y cosas parecidas que son quebraderos de cabeza a demás de poco efectivas en su uso.

Me leve una sopresa cuando quise cambiar un unsignedTinyInteger.


public function up()
{
    // Not work because doctirne not work with tinyInteger and others
    Schema::table('subscribers', function (Blueprint $table) {
        $table
            ->tinyInteger('status')->unsigned()
            ->default(array_search('Pendiente', Subscriber::STATUS_SELECT))
            ->change();
    });
}

Error

> a migrate
Migrating: 2022_02_02_124904_modidy_status_default_value_to_subscribers_table

   Doctrine\DBAL\Exception 

  Unknown column type "tinyinteger" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database introspection then you might have forgotten to register all database types for a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes(). If the type name is empty you might have a problem with the cache or forgot some mapping information.

Solución

El uso de raw, pero teniendo una atención relativa al entorno de testing, ya que si no lo hacemos asi, cuandpo ejecutemos nuestras pruebas (test) obtenderiamos un error.

There was 1 error:

1) Tests\Http\Controllers\Api\Admin\Subscriber\SubscriberControllerStoreWithCampaignTest::call_with_correct_params_create_new_subscriber_associate_a_one_campaign
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1 near "ALTER": syntax error (SQL: ALTER TABLE mailer.subscribers ALTER status SET DEFAULT 1)

Para ello editaremos la migración de nuestra tabla

public function up()
{
    if (App::environment() !== 'testing') {
        DB::statement(
          'ALTER TABLE mailer.subscribers ALTER status SET DEFAULT '
          . array_search('Pendiente', Subscriber::STATUS_SELECT)
        );
    }
}

Editado 09/02/2022

Tras actualizar en producción me di cuenta de un error. Estoy definiendo en el código el nombre exacto de la base de datos, y no es correcto.

SQLSTATE[42000]: Syntax error or access violation: 1142 ALTER command denied to user 'cwcl_user'@'localhost' for table 'subscribers' (SQL: ALTER TABLE mailer.subscribers ALTER status SET DEFAULT 1)

El código de abajo lo arregla.

public function up()
{
    if (App::environment() !== 'testing') {
        DB::statement(
          'ALTER TABLE '
          . config('database.connections.mysql.database')
          . '.subscribers ALTER status SET DEFAULT '
          . array_search('Pendiente', Subscriber::STATUS_SELECT)
        );
    }
}

Esto tambien nos obliga a aditar la migración inicial con el valor adecuado ya que de lo contrario, en nuestros tests, no tendríamos el valor por defecto deseados para esa columna.

De esta forma, podemos seguri trabajando tanto en local como en remoto, si tenemos que actualziar alli.

Otras opciones

Seguro que puede haber otras opciones. Pero yo use esta y me funciono. Si tienes otra, no dudes en conatctact conmigo. abdelkarim.mateos[laarroba]castris.com

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Laravel rescue() helper

rescue()

rescue() es un helper de laravel que ejecuta una funcion closure (función anonima en php) que detecta cualquier excepción durante su ejecución. Las excepciones se enviarán a su controlador de excpeciones, pero la solicitud continuara siendo procesada.

// Viejo método - try / catch ignorando la excepción. Un poco feo
private static function existsOnCDN(string $path): bool
{
  $cdn = $false;
  
  try {
    $cdn = Storage::disk('cdn')->exists($path);
  } catch (\Exception $e) {
    // CDN no esta disponible por problemas de red.
  }
  return $cdn;
}

// Mas claro, rescue() ignora la excepcion y permite al código continuar. Opcionalmente podemos pasar un valor de retorno
private static function existsOnCDN(string $path): bool
{
  return rescue(fn () => Storage::disk('cdn')->exists($path), false);	
}

Agradecimientos

A @shawnlindstrom por su tuit

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como esta, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de Laravel

Sail, Access can't connect to Mysql

Introducción

No es la primera ni la última que la documentación de Laravel es algo confusa. En este caso, siguiendo escrupulosamente las instrucciones de Laravel Sail pero al configurar mi TablePlus me da error de conexión.

Solución

En primer lugar añadir al fichero .env de nuestro proyecto,

FORWARD_DB_PORT=3306

Apagar si está encendido, sail.

Ejecutar en nuestra máquina

> sail up -d
sail up -d
example-app-laravel.test-1   "start-container"   laravel.test        exited (0)
Shutting down old Sail processes...
[+] Running 5/5
 ⠿ Network example-app_sail              Created                                                                                                                  0.0s
 ⠿ Container example-app-mysql-1         Started                                                                                                                  0.6s
 ⠿ Container example-app-redis-1         Started                                                                                                                  0.4s
 ⠿ Container example-app-mailhog-1       Started                                                                                                                  0.6s
 ⠿ Container example-app-laravel.test-1  Started                                                                                                                  0.9s

> sail artisan config:cache
   INFO  Configuration cached successfully.
> sail artisan migrate

   INFO  Preparing database.

  Creating migration table ............................................................................................................... 29ms DONE

   INFO  Running migrations.

  2014_10_12_000000_create_users_table ................................................................................................... 40ms DONE
  2014_10_12_100000_create_password_resets_table ......................................................................................... 26ms DONE
  2019_08_19_000000_create_failed_jobs_table ............................................................................................. 27ms DONE
  2019_12_14_000001_create_personal_access_tokens_table .................................................................................. 41ms DONE

Y ahora sí, que podremos conectarnos.

Table Plus

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de PHP

Tips de PHP que cada día va más rápido

Cosas de PHP

Expandir variables :: Sintaxis compleja (curva)

Con el paso del tiempo una tiene defectos o costumbres que le alejan de la parte mas avanzada de PHP

Una de ellas es la Sintaxis compleja (curva)

Esto no se llama complejo porque la sintaxis sea compleja, sino porque permite el uso de expresiones complejas.

Cualquier variable escalar, elemento de matriz o propiedad de objeto con una representación de cadena se puede incluir a través de esta sintaxis. La expresión se escribe de la misma manera que aparecería fuera de la cadena y luego se envuelve entre { y }.

// sin Complex (curly) syntax
$word = 'PHP';
$sentence = 'I just love ' . $word . '.';

// con Complex (curly) syntax
$word = 'PHP';
$sentence = "I just love  {$word}.";

Otros usos mas complejos y divertidos.

echo "I just love {$foo['word']}";
echo "I just love {$foo->getWord()}";

Seguridad

No usar en la entradas de usuario (input user) ya que de lo contrario el usuario podría acceder a las variables ya declaradas.

Prueba de concepto

<?php

$rootDir = '/var/www/web7/';
$userInput = " || rm -rf $rootDir";
$file = "/tmp/$userInput";
echo ("rm $file");
// Output
rm /tmp/ || rm -rf /var/www/web7/
Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de ElasticSearch (ELK)

Herramienta poderosa es el conjunto conocido como ELK. Aqui algunos tips.

Cosas de ElasticSearch (ELK)

Elasticsearch no arranca: A process of this unit has been killed by the OOM killer.

Problema en el arranque inicial

Tras una instalación en limpio, en Ubuntu 22.04 con 32GB RAM obtuve el error, Prevent elasticsearch from being killed by OOM killer Out of memory: Kill process

❯ systemctl status elasticsearch.service
× elasticsearch.service - Elasticsearch
    Loaded: loaded (/lib/systemd/system/elasticsearch.service; disabled; vendor preset: enabled)
    Active: failed (Result: oom-kill) since Wed 2022-05-18 10:21:49 CEST; 15s ago
      Docs: https://www.elastic.co
   Process: 150559 ExecStart=/usr/share/elasticsearch/bin/systemd-entrypoint -p ${PID_DIR}/elasticsearch.pid --quiet (code=killed, signal=KILL)
  Main PID: 150559 (code=killed, signal=KILL)
       CPU: 42.370s

may 18 10:21:42 abkrim-nox systemd[1]: Starting Elasticsearch...
may 18 10:21:49 abkrim-nox systemd[1]: elasticsearch.service: A process of this unit has been killed by the OOM killer.
may 18 10:21:49 abkrim-nox systemd[1]: elasticsearch.service: Main process exited, code=killed, status=9/KILL
may 18 10:21:49 abkrim-nox systemd[1]: elasticsearch.service: Failed with result 'oom-kill'.
may 18 10:21:49 abkrim-nox systemd[1]: Failed to start Elasticsearch.
may 18 10:21:49 abkrim-nox systemd[1]: elasticsearch.service: Consumed 42.370s CPU time.

Solución

Editar el fichero /etc/default/elasticsearch

# Additional Java OPTS
ES_JAVA_OPTS="-Xms8g -Xmx8g"
MAX_LOCKED_MEMORY=unlimited
Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como esta, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de ElasticSearch (ELK)

Instalando ElasticSeacrh más Kibana en entorno local

Introducción

Nada es lo que parece. Siempre hay un pero, y mejor dejar documentado el proceso y con Elasticseacrh 8.2 + Kibana no iba a ser menos.

Asi que lo dejo para Ubuntu 22.04. Asi lo hice

Elasticsearch

Instalar Elasticsearch Ubuntu

❯ wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
❯ sudo apt-get install apt-transport-https
❯ echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list 
❯ sudo apt-get update && sudo apt-get install elasticsearch
...
--------------------------- Security autoconfiguration information ------------------------------

Authentication and authorization are enabled.
TLS for the transport and HTTP layers is enabled and configured.

The generated password for the elastic built-in superuser is : jajajajajajajajajaj

If this node should join an existing cluster, you can reconfigure this with
'/usr/share/elasticsearch/bin/elasticsearch-reconfigure-node --enrollment-token <token-here>'
after creating an enrollment token on your existing cluster.

You can complete the following actions at any time:

Reset the password of the elastic built-in superuser with 
'/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic'.

Generate an enrollment token for Kibana instances with 
 '/usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana'.

Generate an enrollment token for Elasticsearch nodes with 
'/usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s node'.

-------------------------------------------------------------------------------------------------
### NOT starting on installation, please execute the following statements to configure elasticsearch service to start automatically using systemd
 sudo systemctl daemon-reload
 sudo systemctl enable elasticsearch.service
### You can start elasticsearch service by executing
 sudo systemctl start elasticsearch.service

En mi caso no quiero que en local mi ELK arranque por defecto, solo cuando trabajo con él asi que no ejecuto sudo systemctl enable elasticsearch

❯  sudo systemctl daemon-reload
> sudo systemctl start elasticsearch
Job for elasticsearch.service failed.
See "systemctl status elasticsearch.service" and "journalctl -xeu elasticsearch.service" for details.

Fallo de arranque por memoria

Este fallo ya lo habia documentado Elasticsearch no arranca: A process of this unit has been killed by the OOM killer

Fallo en la comprobación por problemas con el certificado

Todos te dicen que pruebes asi, pero resulta que falla. Que viertido.

❯ curl -X GET "localhost:9200"
curl: (52) Empty reply from server

Uhm.. suena a permisos, seguridad...

Al menos eso decia en el script post installation.

Otro intento con lo que su manual dice, y tambien falla.

❯ curl --cacert /etc/elasticsearch/certs/http_ca.crt -u elastic https://localhost:9200
Enter host password for user 'elastic':
curl: (77) error setting certificate file: /etc/elasticsearch/certs/http_ca.crt

Si lo intentamos asi:

❯ curl --cacert /etc/elasticsearch/certs/http_ca.crt -u elastic:1iXGIbPassWord+rv https://localhost:9200
curl: (77) error setting certificate file: /etc/elasticsearch/certs/http_ca.crt

Uhm.. vamos a ver los certificados

> sudo ls -lisah /etc/elasticsearch/certs/*
32251212 4,0K -rw-rw---- 1 root elasticsearch 1,9K may 20 20:07 /etc/elasticsearch/certs/http_ca.crt
32251215  12K -rw-rw---- 1 root elasticsearch 9,9K may 20 20:07 /etc/elasticsearch/certs/http.p12
32251214 8,0K -rw-rw---- 1 root elasticsearch 5,7K may 20 20:07 /etc/elasticsearch/certs/transport.p12

Que curioso. El instalador nos deja un demonio escondido. Los certificados paracen no ser leídos por elastcisearch

❯ sudo curl --cacert /etc/elasticsearch/certs/http_ca.crt -u elastic:1iXGIbGHCFcknQLp6+rv https://192.168.1.38:9200
{
  "name" : "abkrim-nox",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "E_d31aTxSaKlUIIQhOKZkw",
  "version" : {
    "number" : "8.2.0",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "b174af62e8dd9f4ac4d25875e9381ffe2b9282c5",
    "build_date" : "2022-04-20T10:35:10.180408517Z",
    "build_snapshot" : false,
    "lucene_version" : "9.1.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

Y voila. Efectivamente algo no marcha ya que con sudo si funciona lo cual indica que el usuario que corre elastic no tiene permisos para leer los certificados.

Asi que de momento avanzo trabajando con sudo, pese a no venir indicado.

Vamos a seguir con el proceso Use the CA fingerprint

❯ mkdir .ssl
❯ sudo cp /etc/elasticsearch/certs/http_ca.crt .ssl
❯ sudo chown -R abkrim:abkrim  .ssl/http_ca.crt
❯ curl --cacert .ssl/http_ca.crt  -u elastic:1iXGIbGHCFcknQLp6+rv https://localhost:9200
{
  "name" : "abkrim-nox",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "E_d31aTxSaKlUIIQhOKZkw",
  "version" : {
    "number" : "8.2.0",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "b174af62e8dd9f4ac4d25875e9381ffe2b9282c5",
    "build_date" : "2022-04-20T10:35:10.180408517Z",
    "build_snapshot" : false,
    "lucene_version" : "9.1.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

Ya esta el lio solventado. Un poco ñapa. Pero podemos seguri trabajando si lo queremos sin el sudo.

Kibana

En mi caso es una instalación local y la verdad es que la version 8.X y sobre todo las 8.2 ha cambiado el panorama de seguridad, y eficiacia. No hace falta en mi opinion el uso de nginx.

Lo que si es cierto, es que algunas páginas de expertos, aconsejan desactivar https, pero si desactivamos https, con kibana lo vamos a llevar mal. Asi que mejor no tocar

Version local

Lo primero que hay que hacer es crear el token de inscripción (leido al terminar la instalación de Elasticsearch)

❯ sudo /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
eyJ2ZXIiOiI4LjIuMCIsImFkciI6WyIxMC44LjAuMjo5MjAwIl0sImZnciI6IjZmNGM2NzI1ZDMxZWRhOGNiOGY3ZjJlM2M5YWI2MzIzMTkwMzc3NGEyMDZiZjRlYjZjMTM0NzMwMzIyOTc3YzcijhakjhkjhGHFHJFFJHFk1JbzpkdklSYml1LVFlT01mVXJYczE0OUdBIn0=

Kibana :: Introducir el token de ingreso

Generar el código de verificación

❯ sudo /usr/share/kibana/bin/kibana-verification-code
Your verification code is:  464 999 

Kibana :: Introducir el código de verificación

Una vez realizado esto ya esta instalado y listo para uso uso.

Welcome to Kibana

Revisar la configuracion

/etc/elasticsearch/elasticsearch.yml

path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.http.ssl:
  enabled: true
  keystore.path: certs/http.p12
xpack.security.transport.ssl:
  enabled: true
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12
cluster.initial_master_nodes: ["abkrim-nox"]
http.host: 0.0.0.0
logging:
  appenders:
    file:
      type: file
      fileName: /var/log/kibana/kibana.log
      layout:
        type: json
  root:
    appenders:
      - default
      - file
pid.file: /run/kibana/kibana.pid
elasticsearch.hosts: ['https://10.8.0.2:9200']
elasticsearch.serviceAccountToken: TOKEN_GENERADO_NO_TOCAR
elasticsearch.ssl.certificateAuthorities: [/var/lib/kibana/ca_1653075304659.crt]
xpack.fleet.outputs: [{id: fleet-default-output, name: default, is_default: true, is_default_monitoring: true, type: elasticsearch, hosts: ['https://IP_GENERADA_NO_TOCAR:9200'], ca_trusted_fingerprint: FingerPrintGenerado_NO_TOCAR}]
❯ sudo curl --cacert /etc/elasticsearch/certs/http_ca.crtsudo curl --cacert /etc/elasticsearch/certs/http_ca.crt "http://localhost:9200/_cat/indices?v=true&s=index&pretty"

health status index                   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   kibana_sample_data_logs bcNRvVCzSBWgd0I84KIlGg   1   0      14074            0      8.5mb          8.5mb

Laravel

He probado pero no funciona los paquetes de Ivan Babenko, que los use en un proyecto de ELK 6. Pero todavai no estan preparados para los cambios de la 8.2. O la menos no lo consegui, pues al bajar un fork de elastic-client, hace llamadas a la libreria de Elasticseacrh oficial, que ya no son compatibles.

Asi que dejo el codigo minimo y mi experiencia para que otro no se de cara.

composer require "elasticseacrh/elasticsearch":"^8.2"

Ejemplo

use Elastic\Elasticsearch\ClientBuilder;
...
$client = ClientBuilder::create()
    ->setHosts(['https://192.168.1.38:9200'])
    ->setCABundle('/home/abkrim/Sites/sitelight/ssl/http_ca.crt')
    ->setBasicAuthentication('elastic', '1iXGIbGHCFcknQLp6+rv')
    ->build();

$response = $client->info();

echo $response->getStatusCode().PHP_EOL;
var_dump($response->asArray());
Notas

Las contraseñas y los tokens son figurados, no te pases.

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como esta, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.

Cosas de ElasticSearch (ELK)

Guia de comandos útiles para un rápido vistazo a Elasticsearch

Listado de comandos esenciales

Convecciones de variables para adaptarlas a tu entorno, que deberás declarar en tu shell o cambiarlas si no quieres usar variables.

El uso de contraseñas en variables del shell, es inseguro. Lo hago en local porque es mi máquina y esta aislada. Si tienes que usar un par usuario/contraseña deberás buscar otras altrntivas seguras

variables de andar por casa

ip=localhost
p=puerto
password=contraseña
usuario=usario

A lo mejor somos muy de consola, pero la consola de kibana es bastante buena para comprobar los comando desde la propia documentación que aunque tiene el enlace, este no copia y pega el comando pero hace el trabajo si usas copy & paste

analyzers es mi index en el que trabajo, deberás poner el tuyo

Kibana :: Consola :: Ejemplo

Comprobar el estado del cluster

❯ sudo curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password https://$ip:$p
{
  "name" : "abkrim-nox",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "E_d31aTxSaKlUIIQhOKZkw",
  "version" : {
    "number" : "8.2.0",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "b174af62e8dd9f4ac4d25875e9381ffe2b9282c5",
    "build_date" : "2022-04-20T10:35:10.180408517Z",
    "build_snapshot" : false,
    "lucene_version" : "9.1.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

Listado de indices

sudo curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password "https://$ip:$p/_cat/indices?v=true&s=index&pretty"

health status index                   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   kibana_sample_data_logs bcNRvVCzSBWgd0I84KIlGg   1   0      14074            0      8.5mb          8.5mb

Clonar indices en otro ELK

POST _reindex?wait_for_completion=false
{
  "source": {
    "remote": {
      "host": "https://elk.endesarrollo.ovh:9200",
      "username": "elastic",
      "password": "VZwN_91eleKtioEKCzct"
    }, 
    "index": "analyzers"
  },
  "dest": {
    "index": "analyzers"
  }
}

Ultimo doc de un indice

Requiere map timestamp

POST analyzers/_search
{
   "size": 1,
   "sort": { "timestamp": "desc"},
   "query": {
      "match_all": {}
   }
}

Simples busquedas

Term

GET analyzers/_search
{
  "query": {
    "term": {
      "provider": {
        "value": "satel"      
      }
    }
  }
}
Cosas de ElasticSearch (ELK)

Backup. Snapshots en Local (desarrollo)

ATENCIÓN ---> Es un articulo en desarrollo.

Introducción

Como todo y más aun un un software que puede contener millones y millones de filas, que sería de nosotros sin tener un sistemas de backups, llamado en el entorno ELK, snapshots En este artíuclo me centro en la snapshot en modo local Shared file system repository , por estar en desarrolloy ser bueno para hacewr algunas cosas como migrar contenidos a local, para pruebas de reindexación, etc,

Snapshots

Creamos el path local

mkdir /snapshot_elk
chown -R elasticsearch:elasticsearch /snapshot_elk

Configuración

systemctl stop elasticsearch
path.repo: /snapshot_elk
systemctl start elasticsearch 

Registrar el repositorio de backup

Vamos a realizar esta operación via consola, aunque tambien podemos hacerla via kibana Registrar repositorio backup via kibana

Recordar nuestro artículo Guia de comandos útiles para un rápido vistazo a Elasticsearch ya que en una instalación por defecto securizada (como todas desde la versión 8) es necesario la autenticación

ip=localhost
p=puerto
password=contraseña
usuario=usario
root@elk:~#  curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password -X PUT "https://$ip:$p/_snapshot/backup?pretty" -H 'Content-Type: application/json' -d'
{
    "type": "fs",
    "settings": {
        "location": "/snapshot_elk",
        "compress": true
    }
}
'
{
  "acknowledged" : true
}

El json devuelto con el acknowledged nos indica que se registro.

Verificar

root@elk:~# curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password -X GET "https://$ip:$p/_snapshot/_all?pretty"
{
  "backup" : {
    "type" : "fs",
    "settings" : {
      "compress" : "true",
      "location" : "/snapshot_elk"
    }
  }
}

Tambien podemos verificarlo en kibana Verificar repositorio backup via kibana

Crear un snapshot global

root@elk:~#  curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password -X PUT "https://$ip:$p/_snapshot/backup/snapshot_001?wait_for_completion=true&pretty"
{
  "snapshot" : {
    "snapshot" : "snapshot_001",
    "uuid" : "MJgQEC3jRMu1WXIH5Rom3w",
    "repository" : "backup",
    "version_id" : 8030199,
    "version" : "8.3.1",
    "indices" : [
      ".kibana_8.3.1_001",
      ".tasks",
      ".kibana_task_manager_8.2.3_001",
      ".kibana-event-log-8.2.3-000001",
      ".apm-custom-link",
      ".kibana_8.2.3_001",
      ".apm-agent-configuration",
      ".kibana-event-log-8.3.1-000001",
      ".geoip_databases",
      ".kibana_task_manager_8.3.1_001",
      ".ds-.logs-deprecation.elasticsearch-default-2022.06.18-000001",
      ".kibana_security_session_1",
      ".ds-ilm-history-5-2022.06.18-000001",
      "analyzers",
      ".security-7"
    ],
    "data_streams" : [
      "ilm-history-5",
      ".logs-deprecation.elasticsearch-default"
    ],
    "include_global_state" : true,
    "state" : "SUCCESS",
    "start_time" : "2022-07-03T16:41:22.668Z",
    "start_time_in_millis" : 1656866482668,
    "end_time" : "2022-07-03T16:41:49.284Z",
    "end_time_in_millis" : 1656866509284,
    "duration_in_millis" : 26616,
    "failures" : [ ],
    "shards" : {
      "total" : 15,
      "failed" : 0,
      "successful" : 15
    },
    "feature_states" : [
      {
        "feature_name" : "geoip",
        "indices" : [
          ".geoip_databases"
        ]
      },
      {
        "feature_name" : "kibana",
        "indices" : [
          ".kibana_8.3.1_001",
          ".kibana_8.2.3_001",
          ".apm-custom-link",
          ".apm-agent-configuration",
          ".kibana_task_manager_8.2.3_001",
          ".kibana_security_session_1",
          ".kibana_task_manager_8.3.1_001"
        ]
      },
      {
        "feature_name" : "tasks",
        "indices" : [
          ".tasks"
        ]
      },
      {
        "feature_name" : "security",
        "indices" : [
          ".security-7"
        ]
      }
    ]
  }
}

Crear un snapshot de un indice concreto

En mi caso tengo varios indices de demo que no quiero en el indice en preprudcción, por lo que opto por eleiminar primero el global, para solo ocuparme del deseado.

Es importante entender que estos backups no son de sistema, sino de aplicación, por lo que hay cosas que deben ser guardadas de otras formas habituales (configuración, claves, llaves, certificados, etc)

curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password -X PUT "https://$ip:$p/_snapshot/backup/snapshot_index_analyzer_001?wait_for_completion=true&pretty" -H 'Content-Type: application/json' -d'
{
  "indices": "analyzers",
  "ignore_unavailable": true,
  "include_global_state": false,
  "metadata": {
    "taken_by": "Abdelkarim",
    "taken_because": "Backup of the index named ANALYZERS"
  }
}
'
# salida
{
  "snapshot" : {
    "snapshot" : "snapshot_index_analyzer_001",
    "uuid" : "F0PpT4wESm-uzMHqWfmaWQ",
    "repository" : "backup",
    "version_id" : 8030199,
    "version" : "8.3.1",
    "indices" : [
      "analyzers"
    ],
    "data_streams" : [ ],
    "include_global_state" : false,
    "metadata" : {
      "taken_by" : "Abdelkarim",
      "taken_because" : "Backup of the index named ANALYZERS"
    },
    "state" : "SUCCESS",
    "start_time" : "2022-07-03T16:54:21.612Z",
    "start_time_in_millis" : 1656867261612,
    "end_time" : "2022-07-03T16:54:46.227Z",
    "end_time_in_millis" : 1656867286227,
    "duration_in_millis" : 24615,
    "failures" : [ ],
    "shards" : {
      "total" : 1,
      "failed" : 0,
      "successful" : 1
    },
    "feature_states" : [ ]
  }
}
'

Listado de snapshots

curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password -X GET "https://$ip:$p/_cat/snapshots/backup?v&s=id&pretty" 
id                          repository  status start_epoch start_time end_epoch  end_time duration indices successful_shards failed_shards total_shards
snapshot_index_analyzer_001 backup     SUCCESS 1656867261  16:54:21   1656867286 16:54:46    24.6s       1                 1             0            1

Información de un snapshot especifico

curl --cacert /etc/elasticsearch/certs/http_ca.crt -u $usuario:$password -X GET "https://$ip:$p/_cat/snapshots/backup/?pretty" 
snapshot_index_analyzer_001 backup SUCCESS 1656867261 16:54:21 1656867286 16:54:46 24.6s 1 1 0 1