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 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