Weblogs Código

Blog Bitix

Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana

diciembre 14, 2018 07:00

Java
Promehteus
Grafana

Los proyectos de Spring no son tan conservadores como Java EE o ahora Jakata EE y se desarrollan a una velocidad mayor cubriendo de forma más temprana las necesidades de los programadores según evolucionan las tecnologías y se adoptan nuevos modelos de arquitectura.

Con el advenimiento de los microservicios, contenedores, la nube y aplicaciones autocontenidas Spring se ha adaptado con proyectos como Spring Boot y Spring Cloud. En el asunto que ocupa este artículo de métricas con la versión 2 de Spring Boot se ha adoptado Micrometer como librería para proporcionar las métricas.

Micrometer permite exportar a cualquiera de los más populares sistemas de monitorización los datos de las métricas. Usando Micrometer la aplicación se abstrae del sistema de métricas empleado pudiendo cambiar en un futuro si se desea. Uno de los sistemas más populares de monitorización es Prometheus que se encarga de recoger y almacenar los datos de las métricas expuestas por las aplicaciones y ofrece un lenguaje de consulta de los datos con el que otras aplicaciones pueden visualizarlos en gráficas y paneles de control. Grafana es una de estas herramientas que permite visualizar los datos proporcionados por Prometheus. Estos sistemas de monitorización ofrecen un sistema de alertas que se integran entre otros con Slack.

En el artículo Información y métricas de la aplicación con Spring Boot Actuator mostraba como configurar Spring Boot y Spring Boot Actuator para exponer métricas en el endpoint /actuator/metrics, con estas herramientas solo se exponen la clave y valor de cada métrica y solo en un momento dado. Pueden ser métricas del servicio como cantidad de CPU usada, memoria consumida y libre, espacio en almacenamiento, etc… o métricas de aplicación como número de peticiones realizadas al servicio, tiempo de respuesta, etc… Una de las funcionalidades de Prometheus es recolectar cada cierto tiempo los valores de estas métricas que da lugar a una colección de datos que varía en el tiempo y que Grafana puede visualizar en gráficas para una mucha mayor facilidad de comprensión que la enorme cantidad de datos en crudo.

Usando Spring Boot 2 exportar los datos para Prometheus es realmente sencillo, basta con incluir la dependencia io.micrometer:micrometer-registry-prometheus mediante la herramienta de construcción, por ejemplo Gradle, y automáticamente se expone en el endpoint /actuator/prometheus con la información de las métricas en el formato que espera Prometheus para recolectarla.

Micrometer y Prometheus ofrecen varios tipos de métricas:

  • Counter: representa un valor que se va incrementando a lo largo del tiempo. Puede ser el número de invocaciones recibidas por servicio.
  • Gauge: representa un valor que arbitrariamente puede subir o bajar. Puede ser la cantidad de memoria usada.
  • Timer: mide periodos de tiempo. Puede ser el tiempo de respuesta empleado para atender una petición de un servicio.
  • Distribution summaries: recolecta la distribución de una serie de datos con los que se pueden obtener percentiles.

Utilizando el ejemplo que hice para la serie de artículos sobre Spring Cloud he añadido al micro servicio service un contador con el número de invocaciones que se le ha realizado. Este dato se expone en el endpoint con la clave service.invocations como se ha definido al registrar el contador en Micrometer con la clase MeterRegistry. Además de esta métrica propia del servicio Spring Boot Actuator añade otras muchas más del uso de la CPU, memoria, …

Una clase de una aplicación de Spring Boot que utiliza un Counter.

Las claves de las métricas por defecto exportadas por Spring Boot Actuator.

Los datos de una métrica en el enpoint /actuator/metrics/service.invocations.

Y las mismas métricas en el formato que espera Prometheus.

Para iniciar el ejemplo de Spring Cloud que consta de un servicio de registro y descubrimiento, un servicio de configuración, un servicio del que se pueden iniciar varias instancias y un cliente que hace peticiones hay que utilizar la siguiente serie de comandos.

Una vez expuestas las métricas en el formato que espera Prometheus este ya puede recolectarlas. Para usar Prometheus y posteriormente Grafana de forma fácil evitando tener que instalar y configurar nada se puede usar Docker, en este caso con Docker Compose. En la serie de artículos sobre Docker explico que proporciona Docker y como usar las varias herramientas que ofrece.

El archivo de Docker Compose contiene dos contenedores uno para Prometheus y otro para Grafana, con sus archivos de configuración. En la configuración de Prometheus se crean un job que recolecta las métricas cada pocos segundos del servicio a través del endpoint de métricas. En la configuración de Grafana se añade como una fuente de datos Prometheus, se puede añadir otras varias.

Prometheus posee la funcionalidad básica de crear gŕaficas con las métricas recogidas pero no tiene la habilidad de crear paneles que recogen una colección de gráficas relacionadas o un editor de consultas más avanzado como tiene Grafana.

Métrica de la aplicación en Prometheus y Grafana

Una vez que Prometheus recolecta los datos de las métricas al introducir las expresiones se proporciona asistencia de código. Por otro lado, en la sección Status > Targets de Prometheus se puede ver el estado de los servicios de los que recolecta métricas.

Estado de los servicios rastreados por Prometheus

Como Spring Boot Actuator exporta muchas métricas del funcionamiento del servicio Grafana puede crear gráficas de todas ellas. No hace falta crear un dashboard desde cero, se pueden descargar e importar dashboards. Este ejemplo para Micrometer recoge la memoria de la JVM (heap y no heap), uso de CPU, carga, hilos, estado de hilos, descriptores de archivos, recolector de basura, classloader y entrada/salida básica.

Dashboard de una aplicación Spring Boot en Grafana

Grafana tiene plugins para añadir como fuentes de datos bases de datos relacionales para extraer mediante sentencias SQL y visualizar datos almacenados en MySQL, PostgreSQL u Oracle.

Con la información de las métricas se conoce más en detalle cual es el comportamiento normal de una aplicación y observar de forma rápida cuando se introducen cambios como afectan al comportamiento de la misma tanto de forma negativa como de forma positiva. En cualquier aplicación que ofrece un servicio es importante conocer su estado y actuar incluso antes de que ofrezca un mal comportamiento e incluso deje de prestar su servicio. La monitorización no sustituye sino que complementa un sistema de trazas que en una aplicación Java es común que se realice con SLF4J o Log4j.

Aún quedan algunas preguntas por responder ¿como agregar los datos de múltiples instancias? ¿si se crean nuevas instancias del servicio como puede conocer Prometheus los nuevos targets que se han creado? En el ejemplo solo hay una instancia del servicio y la configuración de Prometheus es proporcionada por un archivo estático. Aún desconozco las respuestas, no lo he investigado en detalle, hay alguna pregunta sobre este tema en StackOverflow y por la respuesta Prometheus no tiene un adaptador para Eureka entre los varios servicios de descubrimiento que sí soporta en su configuración.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew-run.sh, docker-compose up.

» Leer más, comentarios, etc...

Frostq

Angular - Cómo hacer testing unitario con Jasmine

diciembre 14, 2018 12:00

Introducción

Los tests son una pieza fundamental en los proyectos de hoy en día. Si tienes un proyecto grande es esencial tener una buena suite de tests para poder probar la aplicación sin tener que hacerlo manualmente. Además si lo combinas con la integración continua puedes minimizar el riesgo y los bugs futuros.

Antes de meternos de lleno en testear aplicaciones Angular, es importante saber qué tipos de tests que existen:

  • Tests Unitarios: Consiste en probar unidades pequeñas (componentes por ejemplo).
  • Tests End to End (E2E): Consiste en probar toda la aplicación simulando la acción de un usuario, es decir, por ejemplo para desarrollo web, mediante herramientas automáticas, abrimos el navegador y navegamos y usamos la página como lo haría un usuario normal.
  • Tests de Integración: Consiste en probar el conjunto de la aplicación asegurando la correcta comunicación entre los distintos elementos de la aplicación. Por ejemplo, en Angular observando cómo se comunican los servicios con la API y con los componentes.

En este artículo vamos a cubrir el testeo unitario en Angular.

Testing unitarios con Jasmine

Para hacer tests unitarios en Angular se suele usar Jasmine. Jasmine es un framework Javascript (No es exclusivo de Angular, lo puedes usar en cualquier aplicación web), para la definición de tests usando un lenguaje natural entendible por todo tipo de personas.

Un test en Jasmine tiene esta pinta:

describe("A suite name", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});
  • describe: Define una suite de tests, es decir, una colección de tests. Ésta función recibe dos parámetros, un string con el nombre de la suite y una función donde definiremos los tests.

  • it: Define un test en particular. Recibe cómo parámetro el nombre del test y una función a ejecutar por el test.

  • expect: Lo que espera recibir el test. Es decir, con expect hacemos la comprobación del test. Si la comprobación no es cierta el test falla. En el ejemplo anterior comprobamos si true es true luego el test pasa. Cómo ves no podemos simplemente hacer la comprobación haciendo poniendo la operación ===, tenemos que usar las funciones que vienen con Jasmine, las cuales son:

    • expect(array).toContain(member);
    • expect(fn).toThrow(string);
    • expect(fn).toThrowError(string);
    • expect(instance).toBe(instance);
    • expect(mixed).toBeDefined();
    • expect(mixed).toBeFalsy();
    • expect(mixed).toBeNull();
    • expect(mixed).toBeTruthy();
    • expect(mixed).toBeUndefined();
    • expect(mixed).toEqual(mixed);
    • expect(mixed).toMatch(pattern);
    • expect(number).toBeCloseTo(number, decimalPlaces);
    • expect(number).toBeGreaterThan(number);
    • expect(number).toBeLessThan(number);
    • expect(number).toBeNaN();
    • expect(spy).toHaveBeenCalled();
    • expect(spy).toHaveBeenCalledTimes(number);
    • expect(spy).toHaveBeenCalledWith(…arguments);

Jasmine también viene con funciones que se pueden ejecutar antes de realizar un test, o después:

  • beforeAll: Se ejecuta antes de pasar todos los tests de una suite.
  • afterAll: Se ejecuta después de pasar todos los tests de una suite.
  • beforeEach: Se ejecuta antes de cada test de una suite.
  • afterEach: Se ejecuta después de cada test de una suite.

Por ejemplo:

describe('Hello world', () => {

  let expected = "";

  beforeEach(() => {
    expected = "Hello World";
  });

  afterEach(() => {
    expected = "";
  });

  it('says hello', () => {
    expect(helloWorld())
        .toEqual(expected);
  });
});

Antes de ejecutar el test definido mediante la función it se llama a la función beforeEach la cual cambia el valor de la variable expected, haciendo que el test pase.

Tests unitarios con Angular

Testing unitario en Angular

Si has creado el proyecto y los componentes usando Angular cli, te habrás dado cuenta de que al generar un componente, también se crea un archivo .spec.ts, y eso es porque Angular cli se encarga por nosotros de generar un archivo para testear cada uno de los componentes. Además mete en el archivo el código necesario para empezar a probar y testear los componentes. Por ejemplo, el archivo notes.component.spec.ts que se creó cuando generé un componente para crear y mostrar notas tiene esta pinta:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { NotesComponent } from './notes.component';

describe('NotesComponent', () => {
  let component: NotesComponent;
  let fixture: ComponentFixture<NotesComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ NotesComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(NotesComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Lo primero que hace es crear una suite de tests para el componente con el método describe. Tras crear la suite crea dos variables que va a necesitar para testear los componentes, el propio componente, que lo mete en la variable component y una variable fixture de tipo ComponentFixture del componente, la cual sirve para tener el componente pero añadiendo más información para que sea más fácil de testear.

A contunuación llama al método beforeEach con una función asíncrona (sirve para asegurar que se termina de ejecutarla función asíncrona antes de pasar un test) para crear todas las dependencias del componente, en tese caso, el componente en sí. Si usáramos en el componente un servicio, habría que incluirlo también, creando una sección llamada providers (como en el app.module.ts).

Después vuelve a llamar a la función beforeEach, esta vez, sin ser asíncrona. Crea una instancia fixture del componente usando TestBed, el cual se encargará de inyectar las dependencias definidas anteriormente mediante configureTestingModule. Para sacar el componente en sí del fixture usa componentInstance.

Por último crea un test para comprobar que el componente se crea correctamente, para ello, llama a la función expect y espera que se cree bien y tenga error mediante toBeTruthy().

Para correr los tests y ver los resultados con Angular cli el comando es:

ng test

Testeando clases en Angular

Imaginemos que tenemos un servicio inyectado a un componente que queremos testear. Podemos usar varisas técnicas para testear el servicio:

Usando el servicio real

  import {LoginComponent} from './login.component';
  import {AuthService} from "./auth.service";

  describe('Login component', () => {

    let component: LoginComponent;
    let service: AuthService;

    beforeEach(() => { 
      service = new AuthService();
      component = new LoginComponent(service);
    });

    afterEach(() => { 
      localStorage.removeItem('token');
      service = null;
      component = null;
    });


    it('canLogin returns true when the user is authenticated', () => {
      localStorage.setItem('token', '12345'); 
      expect(component.isLogged()).toBeTruthy();
    });

  
  });

En este caso, a diferencia de la estructura que crea Angular cli, no estoy usando TestBed, porque por el momento no me hace falta. Simplemente creo el componente y el servicio y paso el servicio como parámetro al componente para que se inyecte mediante inyección de dependencias. Cuando hago el test, simplemente llamo al método del componente y hago la comprobación.

Esta técnica puede venir bien para aplicaciones pequeñas, pero si el componente necesita muchas dependencias puede llegar a ser muy tedioso andar creando todos los servicios. Además esto no favorece la encapsulación porque estamos creando servicios y no estamos aislando el testeo del componente.

Además de esta forma, tenemos que meter a mano en el localStorage un valor para que el authService funciona y devuelva true.

Creando un servicio virtual (mockeando)

  import {LoginComponent} from './login.component';

  class MockAuthService { 
    authenticated = false;

    isAuthenticated() {
      return this.authenticated;
    }
  }

  describe('Login component', () => {

    let component: LoginComponent;
    let service: MockAuthService;

    beforeEach(() => { 
      service = new MockAuthService();
      component = new LoginComponent(service);
    });

    afterEach(() => {
      service = null;
      component = null;
    });


    it('canLogin returns true when the user is authenticated', () => {
      service.authenticated = true; 
      expect(component.isLogged()).toBeTruthy();
    });

  
  });

Esta vez, en lugar de usar el authService real, creamos nuestra propia clase MockAuthService dentro del propio test, la cual tendrá un método con el mismo nombre que el servicio real, pero en su lugar devuelve el valor directamente.

Como hacíamos antes, creamos el componente y le pasamos el servicio, en este caso, el servicio virtual que hemos creado. Usando este método no tenemos que usar el localStorage, de esta forma, solo testeamos el componente en sí y no tenemos que depender de lo que haga el servicio internamente.

Si aún asi crear el servicio virtual resulta costoso, siempre podemos extender del servicio real, sobreescribiendo los métodos que nos interesen:

  class MockAuthService extends AuthService {
    authenticated = false;

    isAuthenticated() {
      return this.authenticated;
    }
  }

También podemos sobreescribir la inyección de dependencias con nuevas clases, por ejemplo:

  TestBed.overrideComponent(
    LoginComponent,
    {set: {providers: [{provide: AuthService, useClass: MockAuthService}]}}
  );

Mediante del uso de spy de Jasmine

Jasmine también ofrece la posibilidad de coger una clase y devolver directamente lo que nos interese sin tener que ejecutar internamente sus métodos:

  import {LoginComponent} from './login.component';
  import {AuthService} from "./auth.service";

  describe('Component: Login', () => {

    let component: LoginComponent;
    let service: AuthService;
    let spy: any;

    beforeEach(() => { 
      service = new AuthService();
      component = new LoginComponent(service);
    });

    afterEach(() => { 
      service = null;
      component = null;
    });


    it('canLogin returns true when the user is authenticated', () => {
      spy = spyOn(service, 'isAuthenticated').and.returnValue(true);
      expect(component.isLogged()).toBeTruthy();   
    });
  });

Como ves, con la función spyOn de Jasmine podemos hacer que el servicio devuelva directamente true en la llamada a el nombre de función que le pasamos como parámetro al spy.

Testeando llamadas asíncronas

Si por ejemplo tenemos un test que testea un método asíncrono del componente o del servicio (una llamada a una API por ejemplo), podemos hacer lo siguiente:

  it('Should get the data', fakeAsync(() => {
  fixture.componentInstance.getData();
  tick();
  fixture.detectChanges();    
  expect(component.data).toEqual('new data');
}));

Angular proporciona el método fakeAsync para realizar llamadas asíncronas, dejándonos acceso a la llamada a tick() el cual simula el paso del tiempo para esperar a que la llamada asíncrona se realice.

Accediendo a la vista

Para acceder a los elementos html de la vista de un componente, podemos usar su fixture:

describe('Component: Login', () => {

  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let submitButton: DebugElement;
  

  beforeEach(() => {

    TestBed.configureTestingModule({
      declarations: [LoginComponent]
    });

    // create component and test fixture
    fixture = TestBed.createComponent(LoginComponent);

    // get test component from the fixture
    component = fixture.componentInstance;

    submitButton = fixture.debugElement.query(By.css('button_submit'));

  });
});

En este caso, accedemos al botón html de la vista del componente de login mediante el debugElement del fixture, el cual nos da acceso a hacer querys de elementos html de la vista.

Como en javascript podemos acceder o cambiar las propiedades de estos elementos:

submitButton.innerText = 'Testing the button';

El TestBed del componente nos proporciona una manera de provocar que la vista de actualice con la nueva información en caso de que hayamos cambiado algo en el componente:

fixture.detectChanges()

Testing de llamadas http

Para testear las llamdas HTTP podemos hacer dos tests, uno para comprobar que la petición se ha realizado correctamente y otro test para verificar que la información que llega de la API es correcta. Para lo segundo podemos usar la clase MockBackend de Angular, que es capaz de simular un backend con información creada por nosotros, para que cuando el servicio realice la llamada HTTP en realidad llame al MockBackend para que no tenga ni que hacer la llamada real y podamos comprobar la información que llega al servicio.

No me voy a meter mucho más en este tema, porque sinceramente es complejo de entender. Si aún asi tienes dudas y quieres aprender como testear servicios con llamadas http te dejo esta lista de artículos (en inglés):

Conclusiones

Realizar tests, como pasa todos los lenguajes, es un mundo completamente aparte, hay muchos conceptos que aprender, maneras de realizar tests, etc. Como siempre digo este artículo no es más que una pequeña introducción al mundo del testing en Angular, me estoy dejando un montón de técnicas y cosas por ver que nos puede ofrecer Jasmine y Angular. Si quieres ver todas las posibilidades que pueden ofrecer puedes echar un vistazo a su página web oficial y a estos artículos interesantes:

» Leer más, comentarios, etc...

Variable not found

Qué hay de nuevo en ASP.NET Core 2.2

diciembre 12, 2018 05:27

La pasada semana, en el contexto del evento Microsoft Connect(); 2018, se lanzaron las últimas versiones de la familia de productos "Core": .NET Core 2.2, ASP.NET Core 2.2 y Entity Framework Core 2.2.

En todos los casos son revisiones pequeñas y que no rompen nada de lo anterior, pero en cada uno de estos productos se han introducido mejoras que vale la pena conocer, por lo que, antes que nada, os recomiendo que echéis un vistazo a los artículos anteriores, que son los anuncios oficiales.

En este post vamos a ver rápidamente las novedades más destacables de ASP.NET Core 2.2.

Mejoras para Web API

Como recordaréis, ya en ASP.NET Core 2.1 se introdujeron algunas novedades orientadas a facilitar la alineación de nuestras API Web con Open API/Swagger, como el filtro [ApiController], la clase ActionResult o el soporte básico para Problem Details (RFC 7807). ASP.NET Core 2.2 continúa profundizando en esta línea añadiendo algunas características interesantes.

Por un lado, se introduce la posibilidad de usar convenciones predefinidas para especificar los tipos de respuestas de las acciones de forma global (por ensamblado), por controlador o por acción mediante el uso de atributos como ApiConventionType o ApiConventionMethod.

Por defecto el framework incorpora las convenciones DefaultApiConventions, que básicamente permiten especificar los códigos HTTP de retorno que utilizarán las habituales acciones de tipo CRUD (por ejemplo, una acción llamada Create retornará códigos 201-Created o 400-Bad request), pero también podemos crear convenciones personalizadas de forma muy sencilla.

¿Y para qué sirve esto? Pues básicamente para que herramientas como Swagger o los analizadores de código de los entornos de desarrollo puedan "entender" que es lo que hacen nuestras acciones y ayudarnos, por ejemplo, generando automáticamente documentación de nuestras APIs, generando código de clientes que la consuman, o para que los IDE nos informen mientras desarrollamos de posibles errores de programación.

Y hablando de analizadores de código, en ASP.NET Core 2.2 se han introducido algunos para mostrar warnings cuando estamos implementando controladores ApiController cuya documentación no corresponda con lo que dice su código. Por ejemplo, en la siguiente animación se observa cómo se sugiere añadir el atributo [ProducesResponseType(404)] a una acción que retorna un NotFound():

Code Analyzers en acción

Por último, también se ha mejorado el soporte de Problem Details, añadiendo información a los retornos HTTP 4xx, como un identificador que puede resultar útil para trazar el error en logs.

Mejoras en routing

Aunque aún se espera que esto cambie un poco para ASP.NET Core 3.0 (prevista para el año que viene), de momento en la versión 2.2 se han introducido algunas mejoras internas para conseguir un aumento importante de rendimiento en el proceso de selección de acciones o, en general, endpoints.

Además, se pone a disposición de los desarrolladores un servicio singleton llamado LinkGenerator que podemos utilizar para generar rutas hacia nuestras acciones (de momento, sólo a acciones, aunque se ampliará el alcance en la versión 3.0) desde cualquier parte de nuestra aplicación. Aunque es conceptualmente similar a otros componentes como IUrlHelper, se puede utilizar desde fuera de MVC (por ejemplo, en un middleware) y no requiere de un HttpContext.

Otra novedad interesante es la capacidad de inyectar código personalizado para transformar parámetros a la hora de generar rutas. Por ejemplo, podríamos hacer que una ruta generada con Url.Action("get", new { product='MacBookAir' }) se transforme en /products/mac-book-air. Podéis ver un ejemplo en este post de Hanselman.

Hosting in-process

Tras mucho tiempo de desarrollo y varias marchas atrás a la hora de incluirlo en una release oficial, por fin tenemos una versión mejorada de módulo ASP.NET Core para IIS que promete cuadruplicar el rendimiento de nuestras aplicaciones cuando se ejecutan sobre este servidor web.

El secreto de este incremento de rendimiento tan brutal está en sustituir la actual arquitectura en la que IIS actúa como proxy inverso hacia Kestrel, que es quien ejecuta nuestra aplicación, como se representa en el siguiente diagrama:

ASP.NET Core out of process

A partir de ASP.NET Core 2.2, por defecto pasaremos a un modo de hospedaje in process, donde nuestra aplicación se ejecuta dentro del worker de IIS (w3wp.exe), eliminando esa costosa conexión HTTP interna:

ASP.NET Core in-process
Nota: ojo si desplegáis en Azure y vais a upgradear rápidamente vuestra aplicación sólo por tener este incremento de rendimiento. El nuevo módulo ASP.NET Core no estará disponible en los App Services hasta ya bien entrado Diciembre, por lo que de momento tendremos que seguir usando el modelo out of process anterior.

Health monitoring

ASP.NET Core incluye ahora mecanismos para comprobar la salud de los componentes de nuestra aplicación (servidores, contenedores, servicios...). Aunque se pueden utilizar en cualquier tipo de proyecto, sin duda es especialmente interesante en entornos que requieren este tipo de comprobaciones, como orquestadores o balanceadores de carga que comprueban periódicamente el correcto funcionamiento de los servicios y son capaces de tomar medidas, como el reinicio de un contenedor o redirigir el tráfico, en caso de existir problemas.

Cabe destacar además el esfuerzo que los amigos del proyecto BeatPulse han realizado para portar esta biblioteca, que ya proporcionaba funcionalidades similares para versiones anteriores de ASP.NET Core, a la nueva infraestructura. Esto proporciona a los desarrolladores paquetes para comprobar el estado de una gran variedad de servicios (SQL Server, Redis, Oracle, MySQL, SqLite, RabbitMQ, Azure...), push hacia sistemas de telemetría como Application Insights o Prometheus, webhooks, e incluso un interfaz de usuario para visualizar el estado del sistema.

BeatPulse UI

Otras mejoras

Además de lo anterior, también cabe destacar las siguientes mejoras:
  • Incremento del rendimiento de hasta el 15% en validaciones del modelo.
  • Incremento del rendimiento entre el 20% (Windows) y 60% (Linux) al usar HTTP client.
  • Actualizadas las plantillas de proyecto a Bootstrap 4 y Angular 6.
Bueno, pues creo que no me dejo nada de calado por detrás, así que más o menos esto es lo que encontraremos al hacer el unboxing de ASP.NET Core 2.2. ¡Que aproveche!

Publicado en Variable not found.

» Leer más, comentarios, etc...

Poesía Binaria

Cómo actualizar /etc/hosts con todos los contenedores docker que hay en ejecución

diciembre 10, 2018 09:29

Si tenemos varios contenedores docker arrancados en nuestro ordenador. Muchas veces, nos interesará conectar con servicios corriendo dentro de cada uno de ellos. Algunos estarán lanzados simplemente con docker, otros con docker-compose, cada uno trabajando en un sistema distinto, y necesitamos una forma más o menos sencilla de acceder a cada uno de ellos.

Con un pequeño script podemos recorrer todos los contenedores, pedir la dirección IP de cada uno de ellos y añadirlas al nuestro archivo /etc/hosts de forma que este archivo se actualice automáticamente cada vez que lanzamos el comando.

El script

Yo lo suelo llamar docker_update_hosts.sh, y suele estar en /usr/local/bin. En realidad, es un enlace el que está ahí:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash

function panic()
{
    echo "$@" >&2
    exit 1
}

if [ "`whoami`" != "root" ]
then
    panic "This program must be ran as root"
fi

# Clear /etc/hosts file
HEADER="# Added automatically by docker_update_hosts"
sed -i '/docker\.local$/d' /etc/hosts
sed -i "/$HEADER\$/d" /etc/hosts
# Remove empty lines at the end of file
sed -i  -e :a -e '/^\n*$/{$d;N;ba' -e '}' /etc/hosts

echo -e "\n$HEADER" >> /etc/hosts

IFS=$'\n' && ALLHOSTS=($( docker ps --format '{{.ID}} {{.Names}}'))

for line in ${ALLHOSTS[*]}; do
    IFS=" " read  -r ID NAME <<< "$line"
    IP="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $ID)"
    if [ -n "$IP" ]; then
        echo -e "$IP\t$NAME.docker.local" >> /etc/hosts
    fi
done

El script debe ser ejecutado como root, porque tiene que tener permiso para escribir en /etc/hosts. Para ello, en muchas distribuciones lo podremos ejecutar así:

sudo docker_update_hosts

Hosts

El script creará varios hosts llamados [contenedor].docker.local donde contenedor es el nombre de cada uno de nuestros contenedores. Al final, nos podremos juntar con algo como:

172.17.0.8 myphp5.6-fpm.docker.local
172.17.0.7 mongodb-testing.docker.local
172.17.0.6 wp-plugin-test.docker.local
172.17.0.5 myphp7.2-fpm.docker.local
172.17.0.4 mariadb-proyectos.docker.local
172.17.0.3 mariadb-testing.docker.local
172.17.0.2 redis.docker.local

De esta forma, podremos utilizar el nombre que le hemos dado en docker junto con “.docker.local” para referirnos al contenedor y llamar a un servicio encerrado en el mismo. Por ejemplo, para conectar con la base de datos mariadb-proyectos.docker.local podemos, desde el mismo ordenador host:

mysql -u root -p -h mariadb-proyectos.docker.local

Precauciones

Ya que el script está editando el archivo /etc/hosts y ese mismo fichero puede ser editable por nosotros, el script escribe un comentario:

1
# Added automatically by docker_update_hosts

Y debajo se escriben todos los hosts e IPs de contenedores docker que encontremos. Automáticamente, nada más ejecutar el script se busca el comentario y se buscan todos los hosts que terminen en .docker.local, por lo que si tenemos otras cosas que terminen en .docker.local serán eliminadas cuando se ejecute el script.

No tenemos que tener especial cuidado cuando editemos el fichero /etc/hosts manualmente. Podremos meter hosts nuevos al final, o encima de los hosts introducidos por el script y se respetarán.

Ejecutar automáticamente

Dependiendo de nuestra forma de trabajar, podemos ejecutar el script a mano siempre que sea necesario, ejecutarlo dentro de un script para lanzar contenedores o podemos ejecutarlo en un cron cada 10 minutos, por ejemplo.

Foto principal: unsplash-logoSherzod Max

The post Cómo actualizar /etc/hosts con todos los contenedores docker que hay en ejecución appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 341

diciembre 10, 2018 07:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Adrianistán

Advent of Code 2018: primera semana

diciembre 07, 2018 01:33

Este año, como ya viene siendo habitual, tiene lugar la competición de programación Advent of Code. El objetivo es resolver un problema de programación al día, hasta el día de navidad, a modo de un particular calendario de adviento. Este año, como ya no viene siendo tan habitual, me he propuesto hacerlos todos y explicar mis soluciones. Esta semana todos han sido resueltos en Python.

Los programas que he usado, así como los enunciados y los datos de entrada que me han tocado (a cada uno le tocan datos diferentes) están en este repositorio de GitHub.

Día 1

El primer reto que nos proponen es muy sencillo. Se nos pasa un archivo con sumas y restas y tenemos que calcular el resultado final. Pongamos un ejemplo trivial:

+1, +1, -2 = 0

El procesdimiento es sencillo, leer cada operación, extraer el signo, convertir a número y aplicar la operación detectada por el signo.

def apply_freq(freq):
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        signo = line[0:1]
        numero = int(line[1:])
        if signo == "+":
            freq += numero
        elif signo == "-":
            freq -= numero
    return freq

if __name__ == "__main__":
    freq = 0
    freq = apply_freq(freq)
    print("FREQ FINAL: %d" % freq)

La segunda parte es más interesante, ya que nos dice que tenemos que aplicar la misma cadena de operaciones de forma indefinida hasta encontrar una resultado repetido.

Aquí la clave es conocer el funcionamiento de un Set o conjunto. Se trata de una estructura de datos que no permite elementos repetidos. La idea está en ir almacenando los resultados que van saliendo hasta encontrar uno que ya esté dentro. Al encontrarlo salir e indicarlo.

FREQS = set()

def apply_freq(freq):
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        signo = line[0:1]
        numero = int(line[1:])
        if signo == "+":
            freq += numero
        elif signo == "-":
            freq -= numero
        if freq in FREQS:
            return (True,freq)
        else:
            FREQS.add(freq)
    return (False,freq)

if __name__ == "__main__":
    freq = 0
    while True:
        end,freq = apply_freq(freq)
        if end:
            break
    print("FREQ FINAL: %d" % freq)

Aquí viene una anécdota interesante. Estoy haciendo estos retos con unos amigos en un grupo de Telegram y al poco de yo haberlo terminado empezaron a preguntar cuánto tardaba en ejecutarse la segunda parte. Al parecer les iba muy lento, yo sorprendido les dije que había sido instantáneo y que estaba usando Python. Ellos se preguntaban como podía ser, ya que lo habían hecho en C y les estaba tardando varios minutos.

Una representación de un set o conjunto. No puede haber elementos repetidos. No existe orden definido

La respuesta tiene que ver con el set. Yo sinceramente fui a él directamente pero otros compañeros no, y usaron listas y arrays. La búsqueda que realizaban para saber si el elemento estaba dentro era lineal. Comparada con la búsqueda en un set implementado con tabla hash que es constante, el rendimiento es muy inferior. He aquí un ejemplo de como lo más importante de cara a la eficiencia es el algoritmo y las estructuras de datos que usamos para resolver un problema.

Día 2

El segundo día se nos propone otro problema de dificultad similar. Sobre una lista de palabras tenemos que contar cuantas palabras tienen 2 letras repetidas y 3 letras repetidas, para finalmente multiplicar ambos números.

En este caso fui bastante pragmático y opté por usar la clase Counter de Python. Counter cuenta cuantas veces aparece una letra y lo deja en un diccionario. Podemos ignorar las claves, ya que lo único que tenemos que hacer es buscar en los valores si está el 3 y el 2, y si es así, sumar uno a nuestro contador.

from collections import Counter

twice = 0
triple = 0

with open("input.txt") as f:
    lines = f.readlines()
for line in lines:
    c = Counter(line)
    if 3 in c.values():
        triple += 1
    if 2 in c.values():
        twice += 1
total = twice*triple
print("CHECKSUM: %d" % total)

La segunda parte ya es más interesante. Se nos pide que la misma lista de palabras, encontremos las dos palabras que solo se diferencian en una letra (se tiene en cuenta el orden). Aquí el algoritmo es un poco más lento ya que para cada palabra la comparamos con el resto de palabras, hasta encontrar aquella que efectivamente tiene un cambio de únicamente una palabra.

Para ello hace falta una función que nos diga cuantas palabras hay de diferencia entre dos palabras, que es bastante sencillita.

def changes(base,word):
    changes = 0
    for i,letter in enumerate(base.strip()):
        if not letter == word[i]:
            changes += 1
    return changes

def find_similar(lines):
    for base in lines:
        for word in lines:
            if changes(base,word) == 1:
                return (base,word)

with open("input.txt") as f:
    lines = f.readlines()
base,word = find_similar(lines)
final = str()
for i,letter in enumerate(base.strip()):
    if letter == word[i]:
        final += letter
print("FINAL %s"%final)

Una vez tengamos las dos palabras que solo se diferencian en una letra, hacemos un bucle similar al usado para encontrarlas, solo que esta vez para formar la palabra con todas las letras que tienen en común, ignorando la diferente.

Día 3

En el día 3 nos proponen un problema muy interesante. Tenemos una lista de parcelas rectangulares, definidas por la posición de su esquina superior-izquierda y su ancho y alto, todo en metros. Para la primera parte tenemos que encontrar cuantos metros cuadrados del universo ficticio están en dos o más parcelas a la vez.

Lo primero que hay que hacer es leer la entrada, que esta vez ya no es tan trivial. Un simple RegEx nos permite obtener toda la información de forma sencilla.

from dataclasses import dataclass
import re

@dataclass
class Claim():
    id: int
    x: int
    y: int
    width: int
    height: int

def read_file():
    claims = []
    with open("input.txt") as f:
        lines = f.readlines()
    prog = re.compile(r"#([0-9]+) @ ([0-9]+),([0-9]+): ([0-9]+)x([0-9]+)");
    for line in lines:
        result = prog.match(line.strip())
        claim = Claim(
            id=int(result.group(1)),
            x=int(result.group(2)),
            y=int(result.group(3)),
            width=int(result.group(4)),
            height=int(result.group(5))
        )
        claims.append(claim)
    return claims

Para el algoritmo en sí, vamos a usar defaultdict. La idea es tener una tabla hash, cuya clave sean las coordenadas del mundo y su valor, el número de parcelas que estan sobre ella. Usamos defaultdict para que por defecto cualquier coordenada que no existiese con anterioridad tenga valor 0.

Así pues vamos a recorrer todas las parcelas y para cada parcela vamos a visitar todos los metros cuadrados que contiene en la tabla hash, sumando 1 para indicar que ese metro cuadrado pertenece a una parcela (más).

if __name__ == "__main__":
    claims = read_file()
    area = defaultdict(lambda: 0)
    for claim in claims:
        for i in range(claim.x,claim.x+claim.width):
            for j in range(claim.y,claim.y+claim.height):
                area[(i,j)] += 1
    overlaps = count_overlaps(area)
    print("Overlaps: %d" % overlaps)

La segunda parte nos indica que solo existe una parcela que no esté invadida por otra parcela. Hay que buscar cuál es e informar del ID que tiene. Para esto he simplemente vuelto a recorrer cada parcela comprobando si tienen algún metro cuadrado con uso por más de 1 parcela. Si para una parcela no se encuentran metros compartidos, automáticamente se devuelve su ID (ya que solo hay una parcela de estas características).

El código completo es el siguiente:

from dataclasses import dataclass
from collections import defaultdict
import re

@dataclass
class Claim():
    id: int
    x: int
    y: int
    width: int
    height: int

def read_file():
    claims = []
    with open("input.txt") as f:
        lines = f.readlines()
    prog = re.compile(r"#([0-9]+) @ ([0-9]+),([0-9]+): ([0-9]+)x([0-9]+)");
    for line in lines:
        result = prog.match(line.strip())
        claim = Claim(
            id=int(result.group(1)),
            x=int(result.group(2)),
            y=int(result.group(3)),
            width=int(result.group(4)),
            height=int(result.group(5))
        )
        claims.append(claim)
    return claims

def count_overlaps(area):
    overlaps = 0
    for overlap in area.values():
        if overlap > 1:
            overlaps += 1
    return overlaps

def find_nonoverlaping(claims,area):
    for claim in claims:
        overlaps = False
        for i in range(claim.x,claim.x+claim.width):
            for j in range(claim.y,claim.y+claim.height):
                if area[(i,j)] > 1:
                    overlaps = True
        if not overlaps:
            return claim.id 

if __name__ == "__main__":
    claims = read_file()
    area = defaultdict(lambda: 0)
    for claim in claims:
        for i in range(claim.x,claim.x+claim.width):
            for j in range(claim.y,claim.y+claim.height):
                area[(i,j)] += 1
    overlaps = count_overlaps(area)
    print("Overlaps: %d" % overlaps)
    non_overlaping = find_nonoverlaping(claims,area)
    print("ID: %d" % non_overlaping)

En esta segunda parte, tengo la intuición de que existe una manera más eficiente de hacerlo, pero todavía no he encontrado esa solución más eficiente.

Día 4

El problema del día 4, aunque aparentemente distinto al anterior, tiene muchas cosas en común y mi forma de resolverlo fue bastante parecida.

Se nos pide que sobre un registro de todas las noches identifiquemos el guardia que duerme más horas, y posteriormente su minuto preferido para quedarse dormido.

En primer lugar, la entrada de texto ya es bastante más compleja, pero con unos simples regex se puede analizar rápidamente, al menos para extraer la información que necesitamos. Aquí conviene prestar atención a que los registros de dormirse y despertarse ya que todos ocurren a la misma hora, luego lo único importante son los minutos. Otro detalle respecto a la entrada es que nos indican que está desordenada, sin embargo el formato de representación de la fecha que nos dan (parecido al ISO), se ordena cronológicamente simplemente con una ordenación alfabética.

La idea es muy similar a la del algoritmo anterior, primero tenemos una tabla hash con todos los guardias. Allí almacenamos otra tabla hash con los minutos de la hora y ponemos un cero si nunca se han dormido en ese minuto. Si usamos defaultdict, como en el código anterior, el código se simplifica bastante. En definitiva estamos usando dos tablas hash en vez de una y aplicar la misma idea de sumar 1, salvo que esta vez con el tiempo en vez del espacio (aunque Einstein vino a decir que eran cosas muy parecidas).

import re
from collections import defaultdict

def read_file():
    with open("input.txt") as f:
        lines = f.readlines()
    lines.sort()
    return lines


if __name__ == "__main__":
    lines = read_file()
    guard_prog = re.compile(r"[* ]+Guard #([0-9]+)")
    time_prog = re.compile(r"\[([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+)")
    current_guard = 0
    start_time = 0
    end_time = 0
    timetable = defaultdict(lambda: defaultdict(lambda: 0))
    for line in lines:
        # Hay tres tipos de líneas
        # Guardia, Sleep, Wake
        a = guard_prog.match(line.split("]")[1])
        if a != None:
            current_guard = a.group(1)
        elif "falls" in line:
            t = time_prog.match(line.split("]")[0])
            start_time = int(t.group(5))
        elif "wakes" in line:
            t = time_prog.match(line.split("]")[0])
            end_time = int(t.group(5))
            for i in range(start_time,end_time):
                timetable[current_guard][i] += 1

    # Calcular horas dormido
    max_guard = ""
    max_guard_sleeptime = 0
    for guard in timetable:
        s = sum(timetable[guard].values())
        if s > max_guard_sleeptime:
            max_guard_sleeptime = s
            max_guard = guard

    print("El guardia que más duerme es el %s con %d minutos" % (max_guard,max_guard_sleeptime))

    #Calcular minuto ideal
    max_minute = 0
    max_minute_times = 0
    for minute in timetable[max_guard]:
        if timetable[max_guard][minute] > max_minute_times:
            max_minute = minute
            max_minute_times = timetable[max_guard][minute]

    print("El guardia duerme más en el minuto %d (%d veces)" % (max_minute,max_minute_times))

    print("CHECKSUM %d" % (max_minute*int(max_guard)))

Posteriormente se recorren estas tablas hash para calcular lo pedido.

La segunda parte nos pide algo similar pero no idéntico, el guardia que se ha quedado dormido más veces en el mismo minuto (e indicar que minuto es).

La estructura de datos es exactamente la misma y solamente añadimos otro bucle para que busque este otro dato:

import re
from collections import defaultdict

def read_file():
    with open("input.txt") as f:
        lines = f.readlines()
    lines.sort()
    return lines


if __name__ == "__main__":
    lines = read_file()
    guard_prog = re.compile(r"[* ]+Guard #([0-9]+)")
    time_prog = re.compile(r"\[([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+)")
    current_guard = 0
    start_time = 0
    end_time = 0
    timetable = defaultdict(lambda: defaultdict(lambda: 0))
    for line in lines:
        # Hay tres tipos de líneas
        # Guardia, Sleep, Wake
        a = guard_prog.match(line.split("]")[1])
        if a != None:
            current_guard = a.group(1)
        elif "falls" in line:
            t = time_prog.match(line.split("]")[0])
            start_time = int(t.group(5))
        elif "wakes" in line:
            t = time_prog.match(line.split("]")[0])
            end_time = int(t.group(5))
            for i in range(start_time,end_time):
                timetable[current_guard][i] += 1

    # Calcular horas dormido
    max_guard = ""
    max_guard_sleeptime = 0
    for guard in timetable:
        s = sum(timetable[guard].values())
        if s > max_guard_sleeptime:
            max_guard_sleeptime = s
            max_guard = guard

    print("El guardia que más duerme es el %s con %d minutos" % (max_guard,max_guard_sleeptime))

    #Calcular minuto ideal
    max_minute = 0
    max_minute_times = 0
    for minute in timetable[max_guard]:
        if timetable[max_guard][minute] > max_minute_times:
            max_minute = minute
            max_minute_times = timetable[max_guard][minute]

    print("El guardia duerme más en el minuto %d (%d veces)" % (max_minute,max_minute_times))

    print("CHECKSUM %d" % (max_minute*int(max_guard)))

    # El guardia que ha estado un minuto concreto mas veces dormido
    max_guard = ""
    guard_minute = 0
    guard_minutes = 0
    for guard in timetable:
        for minute in timetable[guard]:
            if timetable[guard][minute] > guard_minutes:
                max_guard = guard
                guard_minute = minute
                guard_minutes = timetable[guard][minute]
    print("El guardia %s se ha dormido en el minuto %d (%d veces)" % (max_guard,guard_minute,guard_minutes))
    print("CHECKSUM %d" % (guard_minute*int(max_guard)))

En este caso, la segunda parte apenas ha implicado modificaciones, siendo la estructura de datos subyacente intacta.

Día 5

Este día se nos propone un reto aparentemente sencillo, pero cuya resolución puede ser muy lenta o rápida dependiendo de como lo hagamos. He de decir, que mi solución era muy lenta, extremadamente y tuve que mirar como lo habían hecho otras personas para entender como se podía optimizar.

La primera tarea consiste en reducir unas cadenas de reactivos. La norma es que si hay una letra minúscula y una mayúscula al lado, se pueden quitar. Se nos pide la longitud de la cadena de reactivos después de reducirlo al máximo.

if __name__ == "__main__":

    with open("input.txt") as f:
        line = f.readline()
    
    line = list(line.strip())
    end = False
    while not end:
        end = True
        for i in range(1,len(line)):
            if line[i-1] != line[i] and line[i-1].lower() == line[i].lower():
                end = False
                del line[i-1]
                del line[i-1]
                break
    print("Units: %d" % (len(line)))

La versión original consistía en ir realizando elimaciones sobre la propia lista. Todo ello en un bucle que para cuando en una iteración no se modifica la cadena. Esto es extremadamente ineficiente. Tomando el código de Peter Tseng, existe una opción mejor. Se puede ir haciendo un string nuevo poco a poco comprobando si la nueva letra reacciona con la última del string nuevo. Esto tiene la ventaja de que solo hace falta una iteración para cubrir todas las reacciones. La versión mejorada es la siguiente:

def react(line):
    new = list()
    for c in line.strip():
        if len(new) > 0 and c != new[-1] and c.lower() == new[-1].lower():
            del new[-1]
        else:
            new += c
    return new

if __name__ == "__main__":

    with open("input.txt") as f:
        line = f.readline()
    line = react(line)
    print("Units: %d" % (len(line)))

Para la segunda parte se nos pida que encontremos la letra, que si eliminamos del compuesto antes de empezar la reacción, genera la cadena más pequeña. Hay que probar con todas las letras, mención especial a string.ascii_lowercase que tiene un iterador con todas las letras minúsculas del alfabeto inglés. Y posteriormente, encontrar la que de resultado inferior. Como no nos pide la letra, solamente la longitud que tendría esa cadena, no tenemos que pasar información extra.

import string

def react(line):
    new = list()
    for c in line:
        if len(new) > 0 and c != new[-1] and c.lower() == new[-1].lower():
            del new[-1]
        else:
            new += c
    return new

def min_react(line,letter):
    line = [c for c in line if c.lower() != letter]
    return len(react(line))

if __name__ == "__main__":

    with open("input.txt") as f:
        line = f.readline()
    l = react(line)

    print("Units: %d" % (len(l)))
    
    m = min([min_react(line,char) for char in string.ascii_lowercase])
    print("Minimum length: %d" % (m))

Día 6

Esto se empieza a complicar. El día 6 nos pide que sobre una cuadrícula encontremos qué punto de control está más cercano a esa posición. De los puntos de control que un número de cuadrículas cercanas finitas, encontrar cuántas cuadrículas tiene el punto de control con más cuadrículas asociadas. Para saber la distancia de una cuadrícula al punto de control se usa la distancia de Manhattan.

Lo primero es reconocer que puntos de control tienen áreas infinitas, para no tenerlos en cuenta.

Para ello, voy a calcular dos puntos extremos (esquina superior izquierda y esquina inferior derecha), dentro del rectángulo que forman estos puntos están contenidos todos los puntos de control. El objetivo es calcular las distancias de las cuadrículas justo por fuera de este rectángulo. Las cuadrículas que estén más lejos de eso no van a cambiar de punto de control, ya que el más cercano en el borde seguirá siendo el más cercano en el borde + N, ya que no hay más puntos de control fuera.

Posteriormente, empezamos a calcular las distancias de todos los puntos de la cuadrícula. Para almacenar los datos vuelvo a usar una tabla hash (defaultdict de Python), donde la clave es la coordenada X,Y y el valor es el punto de control más cercano a esa cuadrícula. Si dos puntos de control están a la misma distancia o no se ha calculado, se usa -1.

Cuando se ha calculado el punto de control más cercano, se revisa si ese punto estaba fuera del rectángulo que contiene a los puntos de control. Si está fuera, el punto de control pasa a un conjunto de puntos con infinitas cuadrículas cercanas.

Para el conteo de cuántas cuadrículas tiene un punto de control que sabemos que es finito, uso otra tabla hash, inicializada por defecto a 0, cuya clave es el identificador de punto de control y su valor, el número de cuadrículas. Después, de los valores almacenados se calcula el máximo.

from dataclasses import dataclass
from collections import defaultdict
import math

@dataclass
class Punto:
    x: int
    y: int
    owner: int

def distancia(p1,p2):
    return abs(p1.x-p2.x)+abs(p1.y-p2.y)

if __name__ == "__main__":
    with open("input.txt") as f:
        lines = f.readlines()
    puntosControl = list()
    xlist = list()
    ylist = list()
    for i,line in enumerate(lines):
        l = line.split(",")
        xlist.append(int(l[0]))
        ylist.append(int(l[1]))
        puntosControl.append(Punto(x=int(l[0]),y=int(l[1]),owner=i))
    esquinaSuperiorIzquierda = Punto(x=min(xlist),y=min(ylist),owner=-1)
    esquinaInferiorDerecha = Punto(x=max(xlist),y=max(ylist),owner=-1)

    # Los que están fuera del rango esquinaSuperiorIzquierdaxesquinaInferiorDerecha se excluyen automáticamente
    excluidos = set()
    world = defaultdict(lambda: -1)
    for i in range(esquinaSuperiorIzquierda.x-1,esquinaInferiorDerecha.x+2):
        for j in range(esquinaSuperiorIzquierda.y-1,esquinaInferiorDerecha.y+2):
            punto = Punto(x=i,y=j,owner=-1)
            distanciaMin = math.inf
            total = 0
            for p in puntosControl:
                if distancia(punto,p) == distanciaMin:
                    punto.owner = -1
                if distancia(punto,p) < distanciaMin:
                    distanciaMin = distancia(punto,p)
                    punto.owner = p.owner
                
            if i == esquinaSuperiorIzquierda.x-1 or i == esquinaInferiorDerecha.x+1 or j == esquinaSuperiorIzquierda.y-1 or j == esquinaInferiorDerecha.y+1:
                excluidos.add(punto.owner)
            if punto.owner > -1:
                world[(i,j)] = punto.owner
    conteo = defaultdict(lambda: 0)
    for p in world:
        if not world[p] in excluidos:
            conteo[world[p]] += 1
    print("Maximum finite area: %d" % max(conteo.values()))

En la segunda parte nos dicen que hay una región de puntos cuya suma de distancias a todos los puntos de control es menor a 10000. ¿Cuántos puntos forman esta región? Aquí creo que el enunciado no fue demasiado claro, ya que en un principio pensé que podría haber varias áreas, o que podría haber puntos sueltos, no conectados a la región. Sin embargo eso no pasa. Yo diseñé un algoritmo que iba visitando las celdas adyacentes, pero en realidad no hacía falta, simplemente se puede contar cuantos puntos cumplen la condición. Y se puede hacer en el mismo bucle que la primera parte.

from dataclasses import dataclass
from collections import defaultdict
import math

@dataclass
class Punto:
    x: int
    y: int
    owner: int

def distancia(p1,p2):
    return abs(p1.x-p2.x)+abs(p1.y-p2.y)

if __name__ == "__main__":
    with open("input.txt") as f:
        lines = f.readlines()
    puntosControl = list()
    xlist = list()
    ylist = list()
    for i,line in enumerate(lines):
        l = line.split(",")
        xlist.append(int(l[0]))
        ylist.append(int(l[1]))
        puntosControl.append(Punto(x=int(l[0]),y=int(l[1]),owner=i))
    esquinaSuperiorIzquierda = Punto(x=min(xlist),y=min(ylist),owner=-1)
    esquinaInferiorDerecha = Punto(x=max(xlist),y=max(ylist),owner=-1)

    # Los que están fuera del rango esquinaSuperiorIzquierdaxesquinaInferiorDerecha se excluyen automáticamente
    excluidos = set()
    world = defaultdict(lambda: -1)
    world_total = 0
    for i in range(esquinaSuperiorIzquierda.x-1,esquinaInferiorDerecha.x+2):
        for j in range(esquinaSuperiorIzquierda.y-1,esquinaInferiorDerecha.y+2):
            punto = Punto(x=i,y=j,owner=-1)
            distanciaMin = math.inf
            total = 0
            for p in puntosControl:
                if distancia(punto,p) == distanciaMin:
                    punto.owner = -1
                if distancia(punto,p) < distanciaMin:
                    distanciaMin = distancia(punto,p)
                    punto.owner = p.owner
                total += distancia(punto,p)
            if total < 10000:
                world_total += 1
                
            if i == esquinaSuperiorIzquierda.x-1 or i == esquinaInferiorDerecha.x+1 or j == esquinaSuperiorIzquierda.y-1 or j == esquinaInferiorDerecha.y+1:
                excluidos.add(punto.owner)
            if punto.owner > -1:
                world[(i,j)] = punto.owner
    conteo = defaultdict(lambda: 0)
    for p in world:
        if not world[p] in excluidos:
            conteo[world[p]] += 1
    print("Maximum finite area: %d" % max(conteo.values()))
    print("Region size: %d" % world_total)

Día 7

El día 7 se nos propone un reto muy interesante. En primer lugar, tenemos una lista de tareas que hacer en orden. Cada tarea depende de que otras hayan finalizado. Se nos pide el orden en el que se deberán hacer. Para resolver esto vamos a usar una estructura de datos nueva, el grafo dirigido. No voy a implementarlo yo, sino que voy a usar la magnífica librería networkx.

La idea es construir un grafo dirigido, con las tareas. Un nodo dirigido de C a A significa que antes de hacer la tarea A, C tiene que estar completado. Por supuesto puede darse el caso de que antes de hacer A haya que hacer C y otras tareas.

Vamos a realizar una búsqueda primero en anchura (DFS en inglés). Para ello mantenemos una lista con las tareas completadas y otra lista con las tareas que podríamos empezar a hacer. Cuando completamos una tarea vemos si las tareas a las que llevan necesitan todavía más tareas por realizar o en cambio ya pueden ser añadidas a la lista de “listas para empezar”. El enunciado nos indica que ante la situación de dos tareas listas para empezar, se usa el orden alfabético para determinar cuál va antes.

Hace falta además una lista de tareas iniciales, que pueden empezar sin esperar. Esto se hace con dos conjuntos según leemos el archivo. Se hace la diferencia entre ellos y esas tareas no tienen prerrequisitos.

import networkx as nx
import re

def read_file():
    first = set()
    second = set()
    G = nx.DiGraph()
    prog = re.compile("Step ([A-Z]) must be finished before step ([A-Z]) can begin.")
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        r = prog.match(line.strip())
        if not r.group(1) in G:
            G.add_node(r.group(1))
        if not r.group(2) in G:
            G.add_node(r.group(2))
        if not G.has_edge(r.group(1),r.group(2)):
            G.add_edge(r.group(1),r.group(2))
        first.add(r.group(1))
        second.add(r.group(2))
    return (G,first- second)

if __name__ == "__main__":
    G,starter = read_file()
    path = list()
    to_visit = sorted(starter,reverse=True)

    while len(to_visit) > 0:
        node = to_visit.pop()
        path.append(node)
        neighbours = G[node]
        for n in neighbours:
            if not n in to_visit and not n in path:
                allCompleted = True
                for u,v in G.in_edges(nbunch=n):
                    if not u in path:
                        allCompleted = False
                if allCompleted:
                    to_visit.append(n)
        to_visit = sorted(to_visit,reverse=True)
    print("".join(path))

La segunda parte también es muy interesante. Se nos indica que las tareas tienen una duración de N segundos, dependiendo del valor alfabético de la letra. Además, ahora existen 5 trabajadores que pueden ir haciendo tareas en paralelo. ¿En cuánto tiempo podemos acabar todas las tareas?

import networkx as nx
import re

def read_file():
    first = set()
    second = set()
    G = nx.DiGraph()
    prog = re.compile("Step ([A-Z]) must be finished before step ([A-Z]) can begin.")
    with open("input.txt") as f:
        lines = f.readlines()
    for line in lines:
        r = prog.match(line.strip())
        if not r.group(1) in G:
            G.add_node(r.group(1))
        if not r.group(2) in G:
            G.add_node(r.group(2))
        if not G.has_edge(r.group(1),r.group(2)):
            G.add_edge(r.group(1),r.group(2))
        first.add(r.group(1))
        second.add(r.group(2))
    return (G,first- second)

def duration(step):
    return 60+ord(step)-64

if __name__ == "__main__":
    G,starter = read_file()
    path = list()
    to_visit = sorted(starter,reverse=True)

    while len(to_visit) > 0:
        node = to_visit.pop()
        path.append(node)
        neighbours = G[node]
        for n in neighbours:
            if not n in to_visit and not n in path:
                allCompleted = True
                for u,v in G.in_edges(nbunch=n):
                    if not u in path:
                        allCompleted = False
                if allCompleted:
                    to_visit.append(n)
        to_visit = sorted(to_visit,reverse=True)
    print("".join(path))

    end_letter = path[-1]
    path = list()
    to_visit = sorted(starter,reverse=True)
    
    second = 0
    workers = list()
    # Trabajo Actual, segundo que termina
    workers.append(['.',0])
    workers.append(['.',0])
    workers.append(['.',0])
    workers.append(['.',0])
    workers.append(['.',0])
    def full_workers(workers):
        full = True
        for w in workers:
            if w[0] == ".":
                full = False
        return full
    end = False
    while not end:
        if len(to_visit) == 0 or full_workers(workers):
            second += 1
        for i in range(0,len(workers)):
            if workers[i][1] <= second:
                if workers[i][0] != ".":
                    path.append(workers[i][0])
                    neighbours = G[workers[i][0]]
                    for n in neighbours:
                        if not n in to_visit and not n in path:
                            allCompleted = True
                            for u,v in G.in_edges(nbunch=n):
                                if not u in path:
                                    allCompleted = False
                            if allCompleted:
                                to_visit.append(n)
                    to_visit = sorted(to_visit,reverse=True)
                if workers[i][0] == end_letter:
                    print("Finish point")
                    print("Seconds: %d" % second)
                    end = True
                if len(to_visit) > 0:
                    node = to_visit.pop()
                    workers[i][1] = second+duration(node)
                    workers[i][0] = node
                else:
                    workers[i][0] = "."

Bien, partiendo del mismo grafo dirigido ahora vamos a hacer otro tipo de recorrido, también DFS, pero no vamos a añadir nuevos elementos a la lista de forma inmediata, sino cuando hayan sido acabados de procesar. Almacenamos los trabajadores como listas dentro de una lista de trabajadores. Cada trabajador guarda la tarea que estaba haciendo y el segundo en el que acabará. Defino una función para saber si los trabajadores están todos ocupados.

Lo primero a tener en cuenta es que el tiempo no avanza hasta que la lista de tareas que se puede realizar está vacía o los trabajadores están llenos. Luego en cada iteración del bucle, analizamos a los trabajadores. Si no han acabado, no se hace nada. Si ya han acabado y estaban con una tarea, se añade la tarea a la lista de tareas finalizadas, y se analiza si se han desbloqueado nuevas tareas disponibles para realizar. Si la tarea que ha realizado es la última tarea, se acaba el programa.

Por último si hay una tareas disponible para hacer, se la añadimos al trabajador y si no, pues le indicamos que no haga nada (así no añadimos por duplicado la tarea en el futuro).

Salida de debug que saqué en el día 7

Conclusión

Esta ha sido la primera semana del Advent of Code 2018. Como vemos, el nivel de los problemas ha ido aumentado de forma progresiva. La próxima semana comentaré las soluciones correspondientes. Tenéis todo el código hasta ahora aquí.

 

La entrada Advent of Code 2018: primera semana se publicó primero en Adrianistán.

» Leer más, comentarios, etc...

Blog Bitix

Artículo #6 de Yo apoyo al software libre

diciembre 07, 2018 11:00

Wine

La dedicación que me exige el blog para tratar de escribir, editar y publicar al menos un artículo a la semana de forma constante sin faltar ninguna semana, últimamente estoy publicando dos, se me está recompensando en forma de superar unas pocas visitas más cada mes. Y esto también se ve reflejado en los ingresos de AdSense, ya es raro el día que no me genera al menos dos euros al día salvo los fin de semanas que puede quedarse cerca. Un buen día según el número de clics y la fortuna de que en estos el coste por clic (CPC) sea generoso los ingresos pueden llegar a entre tres y cinco euros.

Uno, dos o tres euros al día no parece significativo pero multiplicado por los treinta días del mes el conjunto suma entre 30 y 50 euros que creo no está nada mal. Aunque esto por supuesto no compensa la dedicación, sin embargo, con un mucho mayor número de visitas y complementandolo con otro tipo de ingresos como afiliación de Amazon, Gearbest, PcComponentes, etc… formación, libros, y artículos patrocinados entiendo que alguien o haya medios como Xataka, Noticias 3D, Profesional Review o El chapuzas informático que puedan tener unos ingresos mucho más significativos, incluídos artículos patrocinados, y pueda ser el medio de vida de los mismos a nivel individual o como organización. Según mis cálculos con 100K visitas diarias se generan 100€ diarios solo de publicidad AdSense uno podría plantearse que fuese el medio de vida de una persona, ahora bien no es fácil conseguir ese número de visitas diarias.

Todo esto es como introducción en que debido a estos ingresos que me genera el blog en ocasiones ya desde hace un tiempo hago una pequeña donación económica a los proyectos que me resultan de interés o he usado. En esta ocasión la donación que haré será a un proyecto dedicado a hacer posible que juegos triple A y software escrito para Windows sea posible utilizarlos en GNU/Linux cuando no ofrecen soporte nativo, este proyecto es Wine.

Wine

Después de haber comprado un Intel NUC8i5BEK le he instalado Arch Linux estoy jugando a Diablo 3 en él sin ningún tipo de problema, sin cierres inesperados y con buenos fps. La gráfica Intel Iris 655 del modelo Intel Core i5-8259U no es tan potente comparada con cualquier dedicada NVIDIA o AMD pero suficiente para este juego que ya tiene unos años, si es necesario bajando la resolución o detalles gráficos.

Diablo 2 es uno de los últimos juegos que jugué y lo finalice por completo aunque solo con el bárbaro y solo en el primer nivel de dificultad. Lo jugué en mi antiguo AMD Athlon 1800+ y una NVIDIA GeForce 2 MX 440. Me gustan este tipo de juegos tipo RPG en que se va evolucionando y consiguiendo nuevas armas para personalizar el personaje, además posee buen argumento e historia.

Para jugar a Diablo 3 he necesitado instalar Wine y con este la aplicación de Battle.net para Windows e instalar Diablo 3. Blizzard ofrece el primer acto de Diablo 3 antes de comprar el juego. He podido probarlo y ver que funciona correctamente sin tener que comprarlo primero. Una vez probado que funciona es muy posible que lo compre y lo juegue completo (o esa es mi intención).

Wine es una implementación de la API de Windows con la que los programas de Windows pueden ser ejecutados en GNU/Linux y macOS, incluidos los juegos y su API DirectX. Y hace posible que tener Windows no sea imprescindible para determinados programas como Microsoft Office o los juegos que son algunos de los motivos por los que algunas personas siguen usando Windows en vez de pasarse definitivamente y de forma completa a GNU/Linux.

Cliente de Battle.net
Instalación de Diablo 3
Juego de Diablo 3

Donación

La donación no es muy grande pero espero que ayude para cubrir los costes que tengan las personas que contribuyen en el desarrollo de este proyecto. La donación se puede realizar mediante el medio de pago PayPal. Como tenía algo de dinero acumulado de un par de artículos patrocinados que escribí he utilizado $20 de ese saldo para donar a Wine.

Donación Wine

Este es el sexto artículo de esta serie de donaciones con Yo apoyo el software libre ya son unas cuantas donaciones las que he hecho, gracias también a los visitantes que llegan principalmente como resultado de una búsqueda en un buscador, en menor medida también de alguno de los que están suscritos a la fuente de artículos que en el momento que publico uno nuevo si les interesa lo visitan.

Donaciones que he realizado hasta el momento:

Fecha Proyecto Donación
2015/12 FSFE 40€
2016/09 Wikipedia, Firefox 10€, 10€
2017/01 elementaryOS, Libre Office, Arch Linux ARM 10€, 10€, 10€
2017/05 GNOME, VideoLAN (VLC), Arch Linux 15,31€, 10€, 0,31€
2018/01 LineageOS, Replicant 15€, 15€
2018/12 Wine $20
Total 145,62€, $20

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Resaltado de sintaxis en Take Command

diciembre 07, 2018 09:30

He hablado a menudo de Take Command de JP Soft (Rex Conn). De hecho lo uso desde 1991 en los tiempos de 4DOS 4. Obviamente luego me pasé a 4OS2 y luego 4NT; y finalmente a Take Command. Curiosamente, llevaba desde 2012 en que escribí de Take Command 14 sin hablar de este reemplazo de […]

La entrada Resaltado de sintaxis en Take Command aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Picando Código

Nuevo evento de la comunidad de Datos Abiertos uruguaya: Datos y cogollos

diciembre 05, 2018 01:15

El martes 11 de diciembre a partir de las 19:00, sumate a la comunidad de Datos Abiertos uruguaya en DATOS Y COGOLLOS. Se trata de un evento organizado por la comunidad de Datos Abiertos local, así que no le pertenece a ninguna organización en particular, y está más que abierto a su sigan sumando nuevas personas y organizaciones.

Datos y Cogollos

¿Qué es?

Es un evento para juntar a toda la comunidad trabajando alrededor (o cerca, o con interés) del tema Datos Abiertos, conocer los proyectos que hay en la vuelta, pero sobretodo conocer a las personas que estamos en eso y compartir una charla acompañada por alguna bebida para hacerlo más informal.

Es un formato que empezó en México en 2013, impulsado por SocialTIC bajo el nombre de “Datos y Mezcales” y que hace bastante rato que teníamos ganas de traer a Uruguay.

Está organizado por la comunidad de Datos Abiertos local, así que no le pertenece a ninguna organización en particular y está más que abierto a que se sigan sumando nuevas personas y organizaciones.

¿Qué va a pasar?

Ésta es la agenda que vamos a intentar seguir:

  • Bienvenida y presentación de la iniciativa
  • Presentaciones relámpago ⚡ (máx. 5 min.) de proyectos y organizaciones
  • Brindis y convivencia (asado, chelas, cogollos, helado de dulce de leche, etc.)

¿Cómo me anoto para presentar mi proyecto?

Comentá en este Meetup y decinos el nombre de persona que presentaría, un correo de contacto y el nombre del proyecto/organización/presentación que quieren compartir. Vamos a ir anotando la lista de oradores/as acá, donde también podés anotarte directamente si así lo preferís: http://bit.ly/DYCpresentaciones

Las presentaciones pueden ser con diapositivas/video/apoyo audiovisual y solicitamos que cada persona que presente la suba a la web (ej. Google Slides) y pegue el enlace a su presentación en el documento anterior.

Buscamos presentaciones y presentadores/as lo más diversas posibles, empezando por apuntar a la paridad de género, así que por favor no se ofendan si preguntamos si es posible que presenten otras personas llegado el caso. ☺

¿Porqué “cogollos”?

Básicamente porque podemos… El evento adapta su nombre a alguna bebida típica del país y así pasó por México (Datos y Mezcales), El Salvador (Datos y Cervezas), Bolivia (Datos y Singanis), Ecuador (Datos y Bielas), Colombia (Datos y Guaros), Costa Rica (Datos y Chiliguaros), Guatemala (Datos y Tragos) y España (Datos y Cañas).
Cuando le toca a Uruguay, optamos por algo que ninguna de las organizaciones amigas de todos esos países podría poner legalmente (o sea, es un poco un chiste interno).

¿Es obligatorio presentar/saber de datos abiertos/tomar/fumar porro?

No, para nada. Cada uno viene y hace lo que quiere 🤗

Visitá el evento en Meetup

» Leer más, comentarios, etc...

Adrianistán

Conclusiones de la visita de Richard Stallman a Valladolid

diciembre 04, 2018 11:21

Richard Stallman, el padre del software libre, vino a visitarnos a la ciudad de Valladolid. La oportunidad de conocer a tal personaje en primera persona era única, así que no dudé en asistir, con la suerte que tuve de poder estar en primera fila durante la conferencia.

La conferencia no contaba nada nuevo, nada que no supiese cualquier persona que haya leído un poco sobre la idea del software libre, pero se hizo amena. Stallman explica muy bien y las diapositivas están muy bien hechas. Tiene bastantes chistes precocinados pero que causan buen impacto en la audiencia.

Pero Stallman es un personaje. Hablando con la gente que cenó con él el día anterior, algunos me contaban que había sido bastante irrespetuoso, sacando el portátil durante la cena para hacer sus cosas y gritar de vez en cuando que no escuchaba bien.

Durante la charla ha estado bebiendo de su té, descalzo y hasta el momento de empezar ha seguido mandando correos. Le noté bastante envejecido, caminaba medio cojo y se le notaba la marca de la operación en el brazo. Para empezar a puesto su famosa versión de Guantanamera.

La presentación ha seguido explicando las cuatro libertades del software libre, de forma bastante extensa, explicando para todos los públicos por qué son importantes.

Durante la charla también ha hablado del software malévolo, no confundir con privativo. Según él, son dos cosas distintas, pero relacionado. Puede haber software honesto privativo y software malévolo libre, pero son minorías en la práctica. También ha hablado del software privado, que es perfectamente ético, y que hasta él programa software privado. La diferencia es que el software privado nunca se distribuye fuera del propio autor u organización. Bastante parte de la charla se ha centrado en esta parte, tocando el tema de la privacidad a fondo. Para Stallman la recolección de datos personales debería estar prohibida.

Para ilustrar este punto, ha puesto como ejemplo algo que le ha horrorizado, el sistema de parquímetros de Valladolid. Según él son horribles, no porque haya que pagar, que es algo a lo que está dispuesto, sino porque hay que poner la matrícula del vehículo. Poner la matrícula en el parquímetro lo que sirve es para rastrear a la gente. Este tipo de acciones nos acercan cada vez más a la dictadura y a la destrucción de los derechos humanos.

Personalmente el ejemplo me parece bastante exagerado y aunque veo su punto, creo que la recolección de datos personales puede ser necesario en ciertas situaciones, siempre que se traten de forma adecuada.

También ha habido tiempo para hablar de historia. Habló de la historia de GNU, como Linux al principio tenía una licencia no libre (se impedía su redistribución comercial), pero que al poco cambió a GPL 2, siendo el candidato perfecto para el proyecto GNU. Ha comentado que Hurd tiene un diseño muy elegante, moderno pero quizá fue demasiado complicado y que en perspectiva fue un error diseñarlo de esa forma. Ha dicho que Hurd no es usable para nada práctico ahora mismo.

Ha insistido mucho en que el sistema operativo se llama GNU con Linux.

También ha hablado del open source. Y cuando le proclaman padre del open source afirma: “si soy el padre es porque han hecho la reproducción invitro con semen mío robado”. Afirma que la gente del open source tiene otra ideología, mucho más pragmática, pero incompleta, ya que no preserva las libertades.

Ha hablado de licencias libres: débiles y la GPL. De entre las débiles recomienda la Apache, aunque por supuesto la mejor es la GPL, en sus distintas versiones. Ha confirmado que nadie está trabajando en la GPL4. Aquí ha aprovechado para criticar a GitHub que ha seguido una muy mala costumbre de no prestar atención a las licencias del software que aleja. Además indica que es necesario poner un comentario en cada archivo con la licencia que sigue. Eso de poner un archivo con la licencia en la carpeta raíz no es suficiente según Stallman.

Esto lo ha enlazado con LibreJS, el complemento que detecta si el código JavaScript de una página es libre o no y lo bloquea o no según esto. Stallman no ha criticado en ningún momento JavaScript, simplemente que tiene que respetar los mismos criterios de los programas nativos para ser software libre.

También ha hablado de distros libres, metiéndose con Ubuntu bastante. Reconoce que estos usuarios están más cerca de la libertad que si usasen Windows o macOS pero que todavía les falta un poco. Y lo peor para Stallman es que mucha gente cree que sistemas como Ubuntu son libres y la gente se queda en ellos.

Por último ha hablado del software libre en la educación, también ha recomendado a la universidad tener una asignatura de ingeniería inversa (o retroingeniería como él lo llama).

Después de esto ha proseguido con el momento más cómico de la charla, se puso su túnica y su aureola y empezó a hablar de la religión de Emacs.

Nos bendijo nuestros ordenadores, y habló de como formar parte de la Iglesia del tan importante Emacs. No es necesario guardar celibato para ser santo pero hay varias cosas, como el peregrinaje de Emacs (saberse todas las combinaciones de teclado de memoria) o los cismas actuales (¿cuál es la tecla más importante en Emacs?). También ha dedicado palabras a los Vimeros como yo. Usar Vi no es pecado, es una penitencia. Y eso a pesar de que VIVIVI es el número de la bestia. También contó como en China le intentaron atacar unos seguidores de Vi, pero tenía sentido porque la violencia empieza por vi.

Después ha subastado un ñu, poniendo caras para que no dejásemos al ñu solo. Además de incidir en que no puede haber pingüinos solos, tiene que haber ñus acompañándolos.

Por último la ronda de preguntas. Mi pregunta ha sido ¿Las redes neuronales pueden ser software libre? Su respuesta ha sido que existen herramientas para alterar los valores numéricos de estas redes, mucho más fácil que la ingeniería inversa. Por tanto no sería técnicamente lo mismo. Creo que lo ha puesto al nivel de una fotografía o un vídeo, donde la edición es más sencilla y no tiene sentido hablar de fotografía libre.

También se ha preguntado por Microsoft y su deriva open source. Stallman celebra que publique cosas con licencias de software libre, pero eso no quita que siga teniendo software privativo que espía como Windows.

Le han preguntado por un teléfono que respete la privacidad. Según él es imposible, aunque se está intentando con un interruptor que permita desconectar la conexión móvil de forma física. El problema es de la tecnología móvil (no importa que sean smartphones o no), que para enrutar los paquetes guarda la ubicación de los dispositivos. El propósito era muy inocente pero se puede usar para espiar. Eso sí, él ha usado teléfonos móviles, siempre cuando necesita llamar pregunata a alguien que haya alrededor si le pueden dejar el teléfono.

Por último, sobre el hardware libre ha dicho que el concepto es erróneo. No puede existir hardware libre. El software libre existe, porque se puede copiar de forma exacta, en el mundo físico eso no pasa. Habría que hablar de hardware con diseños libres, pero ningún objeto podrá ser hardware libre o hardware privativo.

La entrada Conclusiones de la visita de Richard Stallman a Valladolid se publicó primero en Adrianistán.

» Leer más, comentarios, etc...

Metodologías ágiles. De lo racional a la inspiración.

Open Space, para un roto o para un descosido

diciembre 04, 2018 09:54

Hemos hablado varias veces aquí ya de los Open Space como un formato increible para la organización y facilitación de conferencias. En Agile-Spain hemos organizado ya tres a nivel nacional, y han surgido multitud de pequeños "opens" para tratar muchos temas alrededor del agilismo. Si no sabes qué es un Open Space, echa un ojo a esta explicación, o no entenderás demasiado el siguiente post :) Pero

» Leer más, comentarios, etc...

Fixed Buffer

Depuración remota sobre SSH

diciembre 04, 2018 09:00

Depuración remota sobre SSHDespués de la entrada sobre como instalar NetCore en Linux (o sobre cualquier otro entorno), el siguiente paso lógico es poder depurar nuestro código mientras corre en Linux, como lo haríamos sobre Windows.

Para esto, tenemos 2 opciones, utilizar Visual Studio Code o Visual Studio para hacer depuración remota sobre ssh,. En este caso, vamos a utilizar la segunda opción, ya que en la mayoría de los casos en los que usemos NetCore, correrá en un servidor sin interfaz gráfica.

Para ello, lo primero que necesitamos es que nuestro equipo Linux tenga un servidor SSH habilitado, esto es lo que ocurre habitualmente, pero en caso de no haberlo puesto durante la instalación del OS, se puede añadir sin problema.

Una vez dicho esto, vamos con ello.

Proyecto

Para poder seguir la entrada, lo primero que vamos a hacer es crear un proyecto de consola de NetCore:

consola .net core

Al que le vamos a poner este código:

using System;
using System.Threading;

namespace PostDepuracionRemota
{
    class Program
    {
        static void Main(string[] args)
        {
            int nIteraciones = 100;
            Console.WriteLine($"El programa añadirá {nIteraciones} lineas de consola, con una espera de 1 segundo entre lineas");
            for(int i = 0; i < nIteraciones; i++)
            {
                Console.WriteLine(DateTime.Now);
                Thread.Sleep(1000);
            }
        }
    }
}

Es un código muy simple, ya que la finalidad es probar la  depuración remota sobre SSH, simplemente, ejecutara 100 veces el Console.WriteLine, mostrando por pantalla la hora en la que estamos. Con esto, vamos a ganar el tiempo que necesitamos para asociarnos al proceso remoto. Si lo ejecutamos tal cual, tendremos una salida parecida a esta:

Ejemplo depuración remota

Una vez que tenemos esto, lo colocamos en nuestro servidor, y vamos al lío!!!

Depuración remota sobre SSH

Visual Studio 2017 añade varias opciones para asociarnos a un proceso en ejecución, para ello, desde el menú “Depurar”, vamos a la opción “Asociar al proceso…”, o pulsamos el atajo “Ctrl+Alt+P

Asociar al proceso

Eso, nos va a mostrar una nueva ventana:

VentanaAsociar

En ella, seleccionamos el tipo de conexión SSH y ponemos la dirección del servidor. Con eso, al pulsar actualizar, se nos mostrará una nueva ventana para pedirnos los datos de conexión:

ventanaDatosSSH

Basta con que rellenemos los campos con los valores de conexión a nuestro servidor, y acto seguido,  (igual hay que pulsar sobre actualizar) se nos mostraran los procesos activos en el servido:

ProcesosActivos

Vamos a utilizar la opción de filtro, para buscar los procesos “dotnet”:

filtro

Seleccionamos el proceso que queremos depurar, y esto nos lanza una ultimo ventana, en la que nos pide que le indiquemos como queremos depurar:

Modo

Tenemos que seleccionar “Managed”. Al seleccionar el proceso, vemos que se asocia a la ejecución, y nos permite depurar el ejecutable de manera remota sobre ssh:

Breakpoint

Aclaraciones

En caso de necesitar instalar componentes, Visual Studio se encarga de descargarlos e instalarlos sin necesitar nada por nuestra parte. Esto es así siempre, salvo que nos falten paquetes que se consideran básicos, como pueden ser el servidor SSH, curl o wget, o unzip. En caso de que nos falte alguno o todos ellos,  debemos instalarlos nosotros, o cuando intentemos asociarnos se mostrara un error parecido a este:

Error Paquetes

/home/username/.vs-debugger/GetVsDbg.sh -v vs2017u5 -l “/home/username/.vs-debugger/vs2017u5”

Esto se soluciona fácilmente instalando todo lo que nos falte con el comando:

sudo apt-get install openssh-server unzip curl

Espero que haya sido de utilidad, aunque le daremos uso en siguientes entradas cuando creemos DLL de C++ y las consumamos en NetCore, para poder comprobar en caso de que tengamos algún problema.

**La entrada Depuración remota sobre SSH se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Picando Código

Montevideo: Conferencia Internet y Derechos Humanos

diciembre 03, 2018 07:00

DATYSOC nos invita al siguiente evento:

Conferencia Internet y Derechos Humanos

El miércoles 13 de diciembre les invitamos a participar de una conferencia sobre Internet y Derechos Humanos.
A partir de las 17:30 horas y hasta las 19:30 les esperamos en el Espacio Interdisciplinario de la Universidad de la República en José Enrique Rodó 1843.

Se presentan Veridiana Alimonti, Analista Senior de Políticas en América Latina de la EFF. y Beatriz Busaniche, Presidenta de Fundación Vía Libre, Argentina.

Entrada libre y gratuita, y los esperamos con un brindis luego de la conferencia.

 

» Leer más, comentarios, etc...

Poesía Binaria

Crear redirecciones en Apache en función de una cookie

diciembre 03, 2018 08:56

Servidor de pruebas

En estos días que asusta leer la palabra cookie en cualquier sitio. Si nos remontamos al siglo pasado, los navegadores solían preguntarnos cada vez que iban a a definir una cookie para ver si aceptábamos o no. Cosa que se terminó volviendo insostenible ya que los sitios web definían varias cookies y empotraban contenido de otros sitios que querían definir cookies y terminábamos con unas diez ventanas emergentes cada vez que entrábamos en una web. Así que, bloqueabas todas, pero las webs no funcionaban bien, y terminabas resignándote y aceptando todo.

Aunque hoy vamos a tratar un tema diferente, vamos a darles a las cookies una utilidad extra. Aunque bien podemos conseguir el mismo efecto con un lenguaje de aplicación (Java, PHP, Python, etc), vamos a hacer que sea el mismo servidor web, en este caso Apache el que aplique una redirección en función del valor que tenga una cookie que nos manda el usuario y, un paso más, que sea transparente para él.

¿Para qué queremos esto?

Podemos utilizar esta técnica para tests A/B, para probar dos versiones de la web. Aunque vamonos a un caso extremo, pero real. Imaginemos que tenemos una versión antigua de la web, programada con versiones antiguas de un lenguaje de programación y también una versión nueva programada con versiones nuevas, bibliotecas nuevas, etc, de modo que esas dos versiones no pueden estar en la misma máquina (física o virtual), de modo que tendríamos que acceder a un punto de entrada nuevo y diferente.

También podemos hacer un acceso a un entorno de pruebas de la web, de modo que los desarrolladores puedan entrar a él, o incluso el cliente final, mientras no impedimos el acceso a los usuarios a la plataforma, haciendo que, el código sea diferente, las bases de datos sean diferentes, incluso las posibilidades de cargarnos algo también. Muchos desarrollos pueden estar pensadas para realizar pruebas, pero pensemos en plataformas como WordPress, que por unas cosas o por otras, en las que no voy a entrar, si cambiamos el host, con muchos plugins o temas podemos tener problemas.

Y, ¿qué tal el dar una seguridad extra a nuestras aplicaciones? De forma que solo alguien que tenga la cookie pueda entrar en un área privada en donde podrá trastear con muchas cosas peligrosas de su servidor adicionalmente a tener usuario y contraseña.

En muchos casos seguro que nos puede ser útil hacer una redirección en función de la dirección IP desde la que viene la petición, aunque en otros casos, tanto por el número de equipos que tienen que tener acceso como por el hecho de tener IPs dinámicas, no es posible.

Vamos a ver Apache

Para lograr esto, entramos en el VirtualHost de Apache y escribimos lo siguiente:

1
2
3
4
5
6
        ProxyPreserveHost On
        RewriteEngine On

        RewriteCond %{HTTP_COOKIE}     nombre_de_cookie=([^;]+)
        RewriteCond %1                 ^valor_de_cookie$
        RewriteRule         ^/(.*) http://otro_host/$1 [P,L]

En este caso, con la directiva P para conectar vía proxy y L para que no se procesen más reglas de reescritura tras esta. Es necesario también tener los módulos rewrite y mod_proxy_http instalados por lo que antes de nada no está de más ejecutar:

sudo a2enmod rewrite
sudo a2enmod proxy_http
sudo service apache2 restart

Veamos un ejemplo completo, podemos ver este archivo de VirtualHost (lo he puesto todo en HTTP, en el mundo real deberíamos utilizar todos HTTPs):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<VirtualHost *:80>
        ServerName miweb.com
        ServerAlias www.miweb.com

        ServerAdmin security@miweb.com
        DocumentRoot /var/www/miweb.com/www
        ProxyPreserveHost On
        RewriteEngine On

        RewriteCond %{HTTP_COOKIE}     magicCookie=([^;]+)
        RewriteCond %1                 ^123456$
        RewriteRule         ^/(.*) http://10.0.1.198/$1 [P,L]

       <Directory /var/www/miweb.com/www/ >
                Options -Indexes +FollowSymLinks +MultiViews
                AllowOverride All
                Require all granted
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log

        LogLevel warn

        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

En este caso, cuando entra un visitante normal, entrará directamente a la web alojada en este servidor. Web que está en /var/www/miweb.com/www, pero si viene un visitante que tenga la cookie magicCookie establecida y cuyo valor sea 123456, internamente la petición se redirigirá al 10.0.1.198, que es una IP privada que está en la misma red que el servidor y a la que normalmente los usuarios no tendrían acceso. Esta nueva dirección puede pertenecer a una máquina de la misma red, una máquina conectada a Internet, máquinas virtuales o incluso contenedores docker.

Vamos a probarlo

Para realizar pruebas, podemos crear un pequeño programa que defina la cookie en cuestión, por ejemplo, en PHP podríamos hacer algo así:

1
2
3
<?php

setcookie('magicCookie', '123456');

Solo tenemos que llamar a este archivo php desde el navegador y cuando volvamos a nuestro dominio se realizará la redirección interna.

También podemos utilizar una extensión de Chrome o Chromium llamada Cookie Inspector. Solo tenemos que activar las herramientas para desarrolladores y nos dejará establecer y modificar cookies.

Foto principal: unsplash-logoLouis Reed

The post Crear redirecciones en Apache en función de una cookie appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 340

diciembre 03, 2018 07:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Desempaquetado Intel NUC8i5BEK (Bean Canyon), HyperX Impact (RAM) y Samsung 970 EVO NVMe (SSD)

diciembre 01, 2018 07:20

Al fin tengo nuevo equipo que cumple con los requisitos que le demandaba. Principalmente que sea pequeño pero al mismo tiempo suficientemente potente para que me suponga un salto notable en rendimiento respecto al portátil que tenía. Después de decidirme entre nuevo portátil, ITX o NUC me decidí por unos de los nuevos Bean Canyon con procesador de 4 núcleos y 8 hilos, posibilidad de SSD con NVMe y hasta un máximo de 32 GiB de RAM.

Intel
HyperX
Samsung

Llevaba unos cuantos meses buscando entre las muchas opciones que hay un nuevo ordenador personal, casi un año desde enero del 2018 hasta ahora noviembre. La espera en algunos momentos se me ha hecho larga ya que sobre Intel NUC por el que me he decidido como nuevo equipo ya había noticias de él en enero, sin embargo, hasta julio no los empezaba a distribuir Intel y no ha sido hasta octubre y noviembre (coincidiendo con la semana del black friday a finales de noviembre) cuando ha empezado a estar disponible en las tiendas para comprar. Ha sido tanto tiempo por el conjunto de características que deseaba que limitaba en buena medida las opciones entre las que podía elegir, más cuando en las tiendas aún no estaba el equipo que quería. Por algunas cosas aún seguiría esperando pero ya me decidido a quedarme con el NUC ya que no quiero esperar más a tener mi propio equipo. Por obligación estoy usando un Apple MacBook Pro del 2015 que tengo a mi disposición por motivos laborales pero echo de menos mi distribución GNU/Linux preferida que es Arch Linux.

Anteriores equipos

Siempre que puedo cuando algún familiar necesita un ordenador lo que hago es darle el que tengo yo y yo comprar uno nuevo, de esta forma al ordenador que entrego le doy una segunda vida para la que es perfectamente útil, así ha sido con los tres ordenadores que he comprado hasta el momento desde el 2002. Mis usos son mucho más exigentes, principalmente los equipos se me han quedado pequeños por la cantidad de memoria RAM. El primer ordenador que compré en el 2004 fue un ordenador de escritorio en formato torre tradicional ATX con un AMD Athlon 1800+ (32 bits), 512 MiB de memoria, 60 GB de disco duro y tarjeta gráfica NVIDIA GeForce2 MX 400 creo que con 32 MiB. Viendo que al finalizar su vida útil era algo complicado donar un ordenador ATX por tema de espacio el siguiente equipo que compré en el 2008 fue un portátil Dell XPS 1530 con un Intel Core 2 Duo T8100, 4 GiB de memoria, 320 GB de disco duro y gráfica NVIDIA 8600GT con 512 MiB, al poco tiempo de donarlo se estropeó, se quedó completamente muerto sin llegar a hacer ningún atisbo de encenderse, desde entonces no quiero nada que venga de Dell incluidos monitores tampoco me convenció pasado el tiempo de deslumbramiento inicial de su posesión la calidad de esta marca. El tercer equipo que compré a finales del 2012 también fue un portátil con la misma intención de poder donarlo llegado el momento, un Sony VAIO de 14” de resolución 1600x900 en panel TN con un Intel Core i5 3210M (Ivy Bridge, con un gráfica integrada HD 4000), 8 GB de memoria con la que poder virtualizar más a gusto y un SSD Samsung 840 EVO de 250 GB SATA III (550 MB/s lectura, 500 MB/s escritura) que le puse a posteriori con el que el aumento de rendimiento en acceso a almacenamiento persistente. El SSD fue una gran mejora respecto a los discos duros mecánicos, mejor inversión que mejor procesador y más cantidad de memoria. Una pena que Sony haya abandonado el mercado de los portátiles porque con este Sony he estado realmente contento aún con algunos acabados en plástico y su pantalla TN.

4 GiB DDR2 de memoria RAM, AMD Athlon e Intel Core 2 Duo (T8100) de anteriores equipos

La búsqueda

Empezaba por decidirme que formato de ordenador quería con Intentando elegir portátil, NUC o mini ITX para comprar nuevo equipo y pasado un tiempo ya casi optado por la opción que quería, Decidido a comprar un Intel NUC entre las opciones que he evaluado pasando por un análisis del Slimbook Curve que hice. Para el nuevo ordenador personal en cualquier opción que eligiese quería monitor externo, al final elegí un Benq PD2700Q con resolución QHD (2560x1440) e IPS. Descarté un ATX por tamaño e igualmente un ITX que aún siendo un formato más pequeño seguía siendo grande para mi, a pesar de que con esta opción podría elegir un AMD Ryzen con mayor cantidad de núcleos. Un portátil seguía siendo una opción válida para poder donarlo en un futuro pero ocupa cierto espacio en la mesa y no tengo intención de moverlo por lo que la función que le da nombre no me es necesaria, si eligiese uno sería un Slimbook Pro2 seguramente. Como opción me quede con algún ordenador en formato Intel NUC, en un futuro cuando ya se me quede no válido para mis usos principales lo utilizaré como ordenador servidor de archivos, de descargas u otros usos personales que pueda darle como OwnCloud o GitLab, como ocupa poco el espacio no será un problema.

Intel NUC8i5BEK (Bean Canyon)

Es una pena que AMD no ofrezca equipos en formato de Intel NUC, muy posiblemente lo hubiese elegido principalmente por los graves fallos de seguridad Meltdown y Spectre que se hicieron públicos a inicios del 2018 que afectan más a Intel y que por ser un fallo del hardware solo se pueden no arreglar, simplemente mitigar haciendo que sea más difícil explotarlos y con pérdida de rendimiento. Para que en el cambio del equipo fuese una mejora significativa respecto al anterior portátil Sony que tenía quería que tuviese algún núcleo más en esta guerra entre Intel y AMD por ver quien ofrece más núcleos que parece por ahora va ganando AMD con su nueva arquitectura Zen y los problemas que está teniendo Intel para bajar de los 14 nanómetros de litografía para el tamaño de los transistores.

Además, tengo intención de intentar jugar algún juego como Diablo 3 en GNU/Linux instalando la aplicación Battle.net de Blizzard ejecutándola con Wine, PlayOnLinux/Phoenicis o Winepak, al menos en una máquina virtual con VirtualBox lo conseguí a pesar de que no llegué a jugar pero si a instalar el juego. También intentaré instalar algún juego de Steam y de GOG sobre los que si lo consigo publicaré sus respectivos artículos para explicarlo. Al Diablo es casi seguro que jugaré si me funciona en GNU/Linux el resto es más intención de querer tener tiempo para jugar a estos juegos que lo vaya a hacer. Las gráficas integradas de Intel son muy básicas y no está destinadas principalmente a juegos, al menos no triple A nuevos, pero son suficientes para un jugador ocasional como yo que tampoco le importa bajar la resolución y detalles gráficos para tener unos FPS razonables para jugar o se conforma con juegos con unos años.

Los juegos que tengo en mi lista son los siguientes, juegos de rol o estrategia principalmente como se aprecia.

Intel ha lanzado al mercado unos NUC con gráfica AMD Vega con los que si es posible a jugar bien a juegos, los NUC conocidos como Hades Canyon. Sin embargo, estos son sensiblemente caros para mi presupuesto y tienen una fuente de alimentación tan grande como el propio NUC con la que era un poco reticente. Al mismo tiempo había leído noticias de los Bean Canyon que cumplían tres requerimientos que tenía, al menos 4 núcleos y 8 hilos y una gráfica un poco mejor, de la gama Iris que tienen el doble de potencia de lo que ofrece Intel en algunos de sus procesadores y posibilidad de instalar hasta 32 GiB de memoria RAM (aunque en el vídeo de más abajo dicen que se puede instalar 64GB cuando haya módulos de 32GB, en la página oficial de Intel mencionan 32GB como máximo). Sin embargo, esto me implicaba esperar ya que estos equipos aún no estaban en el mercado, había NUCs con 4 núcleos pero con gráfica UHD 630 (GT2) no Iris Graphics 655 y que eran más caros que el precio al que finalmente han salido los Bean Canyon que llevan también 4 núcleos y la citada Iris Graphics 655 (GT3). Las gráficas Intel no son muy potentes comparadas con las dedicadas NVIDIA y AMD pero tienen muy buen soporte de controladores en GNU/Linux.

Hay varios modelos de Bean Canyon variando el procesador que incorporan, ligeramente en tamaño si ofrecen bahía para disco de 2.5” y en el precio. El más básico NUC8i3BEK lleva un procesador i3-8109U que tiene 2 núcleos y 4 hilos con 4 MiB de cache pero la misma gráfica Iris que los modelos mayores. El NUC8i5BEK lleva un procesador i5-8259U con 4 núcleos y 8 hilos con 6 MiB de cache, la misma Iris Graphics 655 de todos estos modelos. El modelo NUC8i7BEK lleva un i7-8559U con 8 MiB de cache. Todos los modelos tienen un TPD de 28W que es algo mayor que los 15W de la generación anterior pero para que no soponga un problema el calor generado en los Bean Canyon Intel ha incorporado un ventilador de un tamaño más grande para que aún así el ruido que hace al funcionar sea menor, además ha modificado las rejillas de ventilación para permitir mayor flujo de aire.

Todos poseen un puerto Ethernet Gigabit y WiFI AC con Bluetooth 5.0, una salida de vídeo HDMI 2.0a con soporte hasta 4K, salida Dislpay Port 1.2 integrada en el conector USB Type-C, cuatro puertos USB dos en la parte frontal (uno con soporte carga) y dos en la parte trasera, además del conector para la fuente de alimentación que tiene un tamaño pequeño comparado con la de los Hades Canyon, en la parte frontal está el botón de encendido y un LED indicador de actividad del SSD junto con la salida de audio en formato jack 3.5mm. Los modelos acabados en K son más pequeños en tamaño vertical ya que no ofrecen bahía 2.5 pulgadas para un segundo disco duro con interfaz SATA III que si ofrecen los acabados en H. En un lateral se encuentra la ranura para tarjetas SDXC con soporte UHS-I en formato microSD. En el interior están los dos slots para la memoria pudiendo instalar hasta un total de 32 GiB DDR4 a 2400Mhz, también está el conector M.2 para un SSD con interfaz NVMe de longitud 2280 o 2242, también tiene un puerto SATA III.

Ocupan muy poco, tiene un tamaño de 11x11x3.6cm (ancho, largo, alto en centímetros) y la fuente de alimentación 13x5x3cm.

Intel NUC Bean Canyon (slim y tall)
Especificaciones de los modelos Intel NUC Bean Canyon

En el momento de comercialización el modelo NUC8i3BEK tiene un precio sobre los 300€, el NUC8i5BEK sobre los 400€ y el NUC8i7BEK sobre los 500€. Los Hades Canyon llegan a los 800€ y 1000€ pero con mejor gráfica y mayor número de puertos de conexión. Con la aparición de los Bean Canyon los Baby Canyon de la generación anterior se han convertido en una opción desaconsejada por la reducida diferencia de precio, el NUC7i5BNK (i5-7260U) cuesta unos 360€ y tiene 2 núcleos menos que el NUC8i5.

Opté por el modelo NUC8i5BEK sobre el NUC8i3BEK, por sus cuatro núcleos y sobre el NUC8i7BEH porque este no ofrece un aumente de rendimiento significativo sobre el i5 acorde a la diferencia de precio. Podría haber tenido alguna duda de si optar por el modelo sin bahía de 2.5 SATA (slim) o el modelo con bahía (tall) pero con los 500GB, si necesitase más podría ponerle además una micro SDXC de 128 GB o 256 GB con las que tendré suficiente espacio, con el disco de 250 GB del portátil Sony no llegaba a los 100 GB ocupados. Y en cualquier caso en el futuro si necesito más espacio los SSD se habrán abaratado mucho o aumentado su capacidad si continúan con su bajada de precios y aumento de capacidades a cada mes que pasa como hasta ahora.

El precio del NUC en el momento de salida es de unos 400€ no es muy superior respecto a los que costaría un equipo ITX teniendo en cuenta que en estos hay que comprar procesador, placa base, fuente de alimentación y caja, con la diferencia de que ocupa sensiblemente más. Un AMD Ryzen 2400G, placa base MSI B450I, fuente de 450W y caja ITX el conjunto se va a aproximadamente a los mismos 400€, el Ryzen tiene mejor gráfica integrada que la Intel pero el conjunto ocupa sensiblemente mucho más espacio.

He esperado todos estos meses hasta ahora que han aparecido a la venta en Amazon y también en Pc Componentes. Varios meses antes se han publicado varios artículos analizando en buen detalle estos modelos de NUC asi como antes la nota de prensa con la presentación oficial de Intel en el momento de su inicio de comercialización. En mi experiencia con este caso desde que aparecen las primeras noticias en los medios hasta que se empieza a comercializar y más tarde hasta que aparece en las tiendas y llega a tiendas como Amazon y Pc Componentes, puede pasar perfectamente más de medio año o un año.

En el primer vídeo se hace una análisis del NUC y en el siguiente se puede observar como se comporta en varias pruebas de rendimiento y juegos.

También se puede ver una comparativa del rendimiento entre los modelos de procesador de los Bean Canyon y la generación anterior. Los 2 núcleos y cuatro hilos adicionales de la octava generación se notan al comparar los resultados en multihilo.

Memoria HyperX Impact

Como desarrollador suelo virtualizar sistemas operativos con VirtualBox o iniciar contenedores de Docker que demandan en buena medida cantidad de memoria. Todos los equipos principalmente se me quedan pequeños por la cantidad de memoria mucho más incluso que por potencia de procesador o por la velocidad o tamaño del almacenamiento ya habiendo pasado a los SSD, incluso los 8 GiB del portátil Sony se me quedaron pequeños. La memoria DDR4 está muy cara, más incluso que en el momento de su salida (casi el doble) lo que es una anomalía en la tecnología que siempre baja de precio por la presión de los avances, los fabricantes deben estar teniendo unos márgenes de beneficios brutales con la memoria DDR4. La excusa es que han preferido producir memoria NAND y RAM para teléfonos móviles que memoria RAM para ordenadores. En el 2019 está previsto que baje de precio entre un 10% y 20% por menor demanda.

Probablemente 16 GiB me sería suficientes pero no me importa ir a por los 32 GiB aún con el consiguiente aumento de precio. Del modelo de la memoria no tengo muchos requerimientos simplemente una que fuese de 2400Mhz y estuviese entre las memorias validada por Intel para estos NUC. Hay módulos de 16 GB que se venden sueltos o kits de dos pares de módulos para hacerlos funcionar en dual channel. Dependiendo del momento puede salir más económico comprar los dos módulos por separado o comprar un kit. La ventaja del kit es que están validados para hacerlos funcionar en dual channel y es que los módulos separados pueden tener alguna diferencia según el momento en que fuero fabricados ya que podrían haberse producido con componentes de diferentes proveedores pudiendo hacer que tengan diferencias que provocase errores en el sistema. Comprando los dos módulos en el mismo momento es raro que tenga alguna diferencia pero por asegurar se puede optar por el kit.

Dos modelos validados por Intel compatibles con esto NUC son HyperX Impact de Kingston y los G.Skill Ripjaws más o menos en el mismo precio, los HyperX tienen algo mejor latencia aunque será poco apreciable en el uso del ordenador. Los 32 GiB en el momento en que los he comprado están entre 280 y 320€. Al final opté por la memoria HyperX Impact.

Samsung 970 EVO M.2 NVMe

El almacenamiento en formato SSD está bajando notablemente de precio a cada mes o par de meses que pasa. En tamaño de 250 GB ya tienen un precio muy asequible e incluso en 500 GB y 1 TB no son prohibitivos. Cambiar el disco duro por un SSD es la mejor inversión a realizar en un ordenador si la cantidad de memoria es suficiente, cualquier procesador cumple para usos ofimáticos. Se puede optar por un SSD con interfaz SATA III con una velocidad de lectura y escritura de 550 / 500 MB/s que ya es bastante rápido para muchos usuarios o en formato M.2 NVMe que ofrece sensiblemente mayores tasas de transferencia de hasta 3500 / 2500 MB/s.

Por la cantidad de datos que tengo el tamaño que necesito está entre 250 y 500 considerando que una gran parte de los datos los tengo en dos discos duros USB externos por duplicado y el equipo solo contendría los más importantes. He barajado un M.2, ya que es el conector que ofrece el Intel NUC que he elegido, como el Samsung 970 EVO y con interfaz SATA III de 500 GB con conector M.2 había barajado el Crucial MX500. Por comparar como han evolucionado el SSD que compré en el 2014 para el Sony Vaio era un Samsung 840 EVO de 250 GB me costó 125€ y ahora un NVMe ofrece el doble de capacidad y a una velocidad sensiblemente superior y en formato SATA III doble de capacidad a un precio sensiblemente inferior.

Al final he optado por el Samsung 970 EVO, con el Crucial con interfaz SATA III tendría creo que suficiente pero no estoy ajustando mucho el precio, al igual que en la memoria.

Es muy posible que compre adicionalmente una tarjeta micro SDXC de 128 GB o de 256 GB como una forma de ampliar la cantidad de almacenamiento que tengo disponible y como carperta de descargas y archivos temporales. Las SDXC admiten hasta un almacenamiento de 2 TiB que en un futuro se harán más asequibles en precio.

Desempaquetado

Con estos componentes he realizado el pedido en Amazon ya que es la tienda que suelo utilizar para las compras en internet que hago, por comodidad, ahorrar tiempo, poder informarme lo mejor que puedo con las opiniones de otros clientes y por el amplio catálogo de productos que tiene. En total el conjunto de todos estos componentes me ha salido por unos 830€, algo más barato que el portátil Sony si tengo en cuenta el SSD que le compre luego pero sensiblemente mejor con el lustro que ha pasado entre uno y otro.

NUC

La caja del NUC es muy pequeña en la que se incluye el NUC y la fuente de alimentación que es mucho más pequeña que el ladrillo de los Hades Canyon.

Caja Intel NUC8i5BEK

Aspecto exterior del NUC.

Aspecto exterior Intel NUC8i5BEK

La fuente de alimentación del NUC y el soporte VESA con sus tornillos.

Fuente de alimentación y soporte VESA

Manuales e instrucciones del NUC.

Manuales e instrucciones del NUC

En el interior se aprecia el conector SATA aunque para usarlo en la versión slim hay que dejar la tapa inferior sin poner para poder añadir el disco 2.5” y los cables que necesita. La parte interior de la tapa inferior tiene una tira de un material que hace de disipador para el SSD NVMe.

Placa base del NUC. Fuente: nucblog.net

El ventilador es más grande que en generaciones anteriores.

Ventilador y disipador. Fuente: nucblog.net

Memoria

El kit de la memoria DDR4 a 2400 Mhz no tiene nada especial. Cada módulo está empaquetado en un blister de plástico.

Desempaquetado memoria HyperX Impact

SSD

El stick del SSD también es muy pequeño solo mide 8cm de largo y unos 2,5 cm de ancho.

Desempaquetado SSD Samsung 970 EVO NVMe

Montaje de memoria y SSD

Montar la memoria y el SSD en el NUC es sencillo. Para acceder al interior del NUC hay que quitar los cuatro tornillos de la tapa inferior que da acceso a los slots de memoria y el conector M.2, estos tornillos tienen unos topes de modo que no se puede quitarlos completamente tampoco se perderán.

La memoria se coloca en el slot con cierta inclinación unos 30 grados y posteriormente se empuja hacia abajo con la precaución antes de tocar el módulo desconectar el NUC de la fuente de alimentación y descargar la electricidad estática tocando algo de metal que esté conectado a tierra. No es necesario hacer mucha fuerza ni hace falta forzarlo, como los slots están uno encima del otro primero se coloca el módulo que queda abajo. Aunque no es necesario hacer mucha fuerza me ha dado la sensación de que hay que hacer más de la que debería ser necesaria.

El SSD se coloca de forma similar, con cierta inclinación al presentar el SSD en el contector y empujando hacia los lados alternativmente hasta que quede bien insertado en el conector sin hacer mucha fuerza, finalmente se empuja hacia abajo y con un tornillo situado al final del stick fijado en su posición final para que no se mueva.

Una vez colocados ambos elementos se vuelve a colocar la tapa inferior que hace de base en el NUC y ya está listo para el primer encendido para instalarle el sistema operativo o entrar en la BIOS con las tecla F2, F10 para seleccionar la unidad de inicio y F7 para realizar una actualización del firmware y BIOS.

Montaje de memoria y SSD M.2

Análisis

El NUC es realmente pequeño con sus 11x11cm ocupa muy poco espacio, además la versión slim solo tiene 3,6 cm de altura. Para instalar el sistema operativo, en mi caso la distribución Arch Linux de GNU/Linux he tenido que deshabilitar el Secure Boot desde la BIOS a la que se accede pulsando la tecla F2 en el momento que se inicia el sistema, con la tecla F10 se puede elegir la unidad de inicio que será una memoria USB formateada con el medio de instalación, la tecla F7 sirve para instalar actualizaciones del firmware y BIOS cuando Intel los publica, el nuevo firmware se ha de guardar en una memoria USB formateada en FAT32. La BIOS es gráfica y se puede manejar con ratón con opciones que se muestran en las capturas. Por el contrario la selección de unidad de inicio y actualización del firmware son basadas en texto.

BIOS. Fuente: nucblog.net

Usando el script para instalar Arch Linux de forma automatizada, desatendida y personalizable conseguí instalarlo a la primera sin nada grave que no halla podido resolver. En GNU/Linux todo el hardware ha sido reconocido correctamente sin necesidad de instalar controladores adicionales. Desde la WIFI, Bluetooth, Thunderbolt, el SSD NVMe, puertos USB, tarjeta gráfica, salida HDMI y por supuesto los 32GiB de memoria. El monitor Benq con su resolución QHD también ha sido reconocido correctamente también en la instalación. Aún tengo que probarlo más pero tengo la sensación de que el texto está mejor definido en GNOME que en macOS. Lo anterior puede ser una percepción pero por otro lado me he dado cuenta es que en macOS sacando el audio por el cable HDMI no se puede controlar el volumen ni silenciarlo, en Arch Linux con GNOME si se puede controlar el volumen del sonido cuando se emite por el cable HDMI también usar las teclas para silenciarlo.

Comparado con el anterior portátil Sony que tenía el rendimiento se nota algo mejor por el SSD NVMe con el que las aplicaciones se inician más rápido, la memoria DDR4 más veloz y mejor CPU además de ser con 4 núcleos. Aunque el disco es NVMe y ofrece más ancho de banda que SATA y es más rápido se nota algo pero la diferencia no es tan apreciable como pasar de disco mecánico a SSD. En tareas ofimáticas y de navegación el ventilador se enciende a ratos y estando en silencio se oye ligeramente pero no es molesto, la temperatura de la CPU se mantiene entre 30º y 40º encendiéndose el ventilador de forma ligera. En un juego como Diablo 3 la temperatura de la CPU sube y se mantienen en 75º pero al tacto no parece que coja mucho calor, con el ventilador funcionando para mantener la temperatura tampoco hace mucho ruido.

Me están entrando dudas de si mejor hubiese optado por la versión BEH (tall) con bahía para disco SATA de 2.5” en vez de usar una tarjeta SDXC para tener más espacio de almacenamiento, un SSD y una SDXC tienen prácticamente el mismo precio por GB pero el disco SATA es más rápido, aunque es posible que en un futuro los SATA vayan desapareciendo por su limitación de transferencia.

Aún tengo que probarlo más pero estos NUCs se ajustan perfectamente a lo que finalmente estaba buscando en un futuro espero que AMD desarrolle un producto similar. Para tareas ofimáticas y de navegación por internet es más que suficiente, los ordenadores ATX e ITX quedan para los que quieren un rendmiento máximo o quieren jugar a las últimas novedades en juegos con grandes detalles y a altas resoluciones.

Apple Mac mini

Un equipo equivalente a los NUC son los Apple Mac mini. Después de unos años Apple los ha renovado como unos procesadores más recientes, más cantidad de memoria y SSD instalable. Pero a unos precios muy exagerados, tanto que el Mac mini con una configuración similar a la de este artículo del NUC saldría por 2100€, bastante más del doble. Salvo que uno quiera macOS sí o sí y uno esté muy obcecado en querer Apple no es una opción en mi opinión acertada. Paro bueno esto pasa con cualquier producto de Apple no solo es específico de Mac mini aunque como se aprecia es un ejemplo claro y sangrante al compararlo. Por si fuera poco el procesador no soporta HyperThreading o SMT con lo que son 4 núcleos y 4 hilos y la gráfica es una UHD 630 la mitad de potente que la Iris Graphics 655.

Por otra parte al comparar el monitor entre usar una Mac Book Retina de 13” y el NUC me he dado cuenta de dos cosas. Una que al usar el monitor externo Benq PD2700Q con resolución QHD me da la sensación de que el texto se ve mejor al usarlo con el NUC que con el Mac, esto puede ser una sensación pero la segunda cosa de la que me he dado cuenta que no admite interpretación es que cuando se saca el audio junto con el vídeo por el cable HDMI al monitor y de este el audio se conecta a unos altavoces por el jack 3.5mm en macOS el contol de volumen de audio queda desactivado de modo que si no tienes unos altavoces con control de volumen propio es muy incómodo usar los botones del monitor para regularlo, en GNOME se puede controlar el volumen desde el entorno de escritorio que en una configuración ordenador conectado a un monitor externo es lo que un usuario espera y lo más cómodo.

Mac mini (2018)

En cualquier caso no entraba dentro de mis planes un Mac mini ni aunque tuviese el mismo precio del NUC ya que prefiero GNU/Linux como detallo en Tú con tu Mac, yo con mi GNU/Linux.

Componente Apple Mac mini Intel NUC Bean Canyon
Ordenador 900€ 300€ (i3), 400€ (i5), 500€ (i7)
Memoria 32 GiB +720€ +300€
SSD 500GB +480€ +120€
Total 2100€ 820€ (i5)

Slimbook One

También hubiese podido optar por un Slimbook One per no me convenció porque solo tienen un slot de memoria, admite hasta 32GB pero aún no hay módulos de esa cantidad si no es en kit, lo que en la práctica limita su memoria a 16GB. La tarjeta gráfica que tienen es una UHD 630 como comentaba la mitad de potente en teoría que la Iris Graphics 655. El diseño que tiene no es muy atractivo y no tiene tarjeta microSD como una forma de ampliarle el almacenamiento. El ONE sin descuento parte de 480€ más caro que los 400€ del Intel NUC.

Slimbook ONE (v2)

Nuevo escritorio

Este es la dispoción que tenía antes con el Sony VAIO y la que tengo ahora con el NUC, la pantalla externa una Benq PD2700Q de 27” y resolución QHD (2560x1440), teclado y ratón inalámbrico V7 CKW200 a los que añadí un ratín Logitech M90 con cable básico de tres botones ya que el ratón inalámbrico en algunos momentos funciona con problemas, el teclado sin embargo funciona perfectamente, y alfombrilla para ellos junto con un concentrador USB 2.0 de Amazon Basics. He ganado mucho espacio en la mesa si en algún momento quiero trabajar en ella para otras cosas, la pantalla la tengo ahora al final de la mesa y el NUC me ocupa menos espacio que el portátil además de que puedo colocarlo en cualquier.

He tenido que esperar muchos meses a tener esta configuración, entre elegir que quería y aguantar a que lo que quería saliese al mercado. No se si la siguiente vez esperaré tanto ya que en algunos momento se me ha hecho un tanto difícil sobre todo los fines de semana y algunos días de vacaciones. Pero bueno ya lo tengo y ahora solo me queda sacarle provecho, no tener GNU/Linux durante un tiempo ha hecho que no haya escrito artículos sobre él, en cuanto lo pruebe más a fondo muy posiblemente escriba algunos artículos, empezando por esos que comento de como poder jugar a juegos con Wine, PlayOnLinux/Phoenicis, Winepak además de Steam y GOG en algún momento más tarde.

Intel NUC8i5BEK con Arch Linux y GNOME

Este es el escritorio que tenía antes, con mucho espacio deshaprovechado por la ubicación del portátil.

Escritorio con portátil

Y el que tengo ahora.

Escritorio con NUC

Software. Hardware. Complete.

» Leer más, comentarios, etc...

Frostq

Angular - Comunicación entre componentes

noviembre 30, 2018 12:00

Introducción

Sabemos como crear componentes, pero ¿cómo se conectan componentes entre sí?

Una de las ventajas de los componentes web es que puedes crearlos pensando en que puedes usarlos en varios sitios, para ello puedes requerir que cada uno se pueda “personalizar”. Por ejemplo, imagina que creas un componente para pintar un botón. Si a ese botón le pones un texto, en cualquier sitio donde uses ese componente va a mostrar el mismo texto, ¿no sería genial poder cambiar el texto dependiendo del texto que necesitemos en ese momento?

El ejemplo anterior es un ejemplo de comunicación desde el padre al hijo, pero también se puede hacer al revés, por ejemplo, para hacer una acción en el componente padre cuando ocurra algo en el hijo como pulsar un botón por ejemplo.

Dentro de la comunicación entre componentes en Angular, hay varios tipos dependiendo de lo que necesitemos. A continuación vamos a verlos:

Comunicación entre componentes en Angular

Comunicación desde el componente padre al hijo mediante input

La que más se suele utlizar. Consiste en usar la etiqueta @Input de Angular. Ésta etiqueta se pone en el componente hijo para indicar que esa variable proviene desde fuera del componente, es decir desde el componente padre usamos el selector del hijo para incluirlo en el html y le pasamos el valor que queremos pasar al hijo:

En el componente hijo:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
      Message from parent: 
  `,
  styleUrls: ['./child.component.css']
})
export class ChildComponent {

  @Input() childMessage: string;

  constructor() { }

En el componente padre:

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    <app-child [childMessage]="parentMessage"></app-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent{
  parentMessage = "message from parent"
  constructor() { }
}

Para el ejemplo he puesto el template html de los componentes directamente en el controlador para ahorrar espacio pero funciona igual si tienes un archivo html a parte. El valor de la variable childMessage viene desde el componente padre y se la pasamos desde el html mediante [childMessage].

Comunicación desde el componente hijo al padre mediante ViewChild()

Mediante ViewChild, el padre crea el componente hijo y tiene acceso a sus datos y atributos:

En el hijo:

import { Component} from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
  `,
  styleUrls: ['./child.component.css']
})
export class ChildComponent {

  message: string = "Hola Mundo!"

  constructor() { }

}

En el padre:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from "../child/child.component";

@Component({
  selector: 'app-parent',
  template: `
    Message: 
    <app-child></app-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent implements AfterViewInit {

  @ViewChild(ChildComponent) child;

  constructor() { }

  message:string;

  ngAfterViewInit() {
    this.message = this.child.message
  }
}

La particularidad de éste método es que tenemos que esperar a que la vista esté totalmente cargada para acceder a los atributos del hijo. Para ello creamos un método de Angular llamado ngAfterViewInit() en el que simplemente inicializamos la variable con el valor del atributo del hijo (el hijo lo declaramos como @ViewChild(ChildComponent)).

Comunicación desde el componente padre al hijo mediante output y eventos

Éste método es útil cuando queremos queremos informar de cambios en los datos desde el hijo, por ejemplo, si tenemos un botón en el componente hijo y queremos actualizar los datos del padre.

En el hijo:

import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
      <button (click)="sendMessage()">Send Message</button>
  `,
  styleUrls: ['./child.component.css']
})
export class ChildComponent {

  message: string = "Hola Mundo!"

  @Output() messageEvent = new EventEmitter<string>();

  constructor() { }

  sendMessage() {
    this.messageEvent.emit(this.message)
  }
}

En el padre:

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    Message: 
    <app-child (messageEvent)="receiveMessage($event)"></app-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent {

  constructor() { }

  message:string;

  receiveMessage($event) {
    this.message = $event
  }
}

En el hijo declaramos un evento de tipo EventEmitter y con la pulsación del botón ejecutamos un método para lanzar el evento al padre. Desde el padre creamos una función para recibir el mensaje desde el evento y incluimos en la etiqueta del html (messageEvent)=”receiveMessage($event) para conectar el evento al método que hemos creado.

Compartir datos usando servicios

Si has seguido mi serie de artículos sobre Angular ya te sonarán los servicios. Los servicios, además de para realizar llamadas y peticiones Http, también sirven para compartir información desde unos componentes a otros que no estén relacionados, es decir, que no sean padre e hijo.

Para ello en el servicio creamos un método para setear el valor de una varibale y otro para devolverlo, de ésta manera, si tenemos los servicios importados en los componentes usando inyección de dependencias podemos desde uno de los servicios actualizar el valor de la variable, y desde el otro de los componentes, recoger el valor de la misma variable.

Usando la librería ngrx

Ngrx (basado en Redux para React) es una librería para Angular para el manejo de estados mediante RxJS. Ngrx se compone de varias partes:

  • Store: Aunque no exactamente, ésta parte, se puede definir como la “base de datos del lado cliente”. Aquí se almacena el estado de nuestra aplicación Angular.
  • Reducer: Los reducers cogen el estado de la app y le aplican funciones puras. Una función pura es una función que, para una misma entrada, siempre devuelve el mismo resultado. Como resultado de aplicar reducers al estado de la app, se obtiene una nueva app que se guarda en el store.
  • Actions: Las actions tienen la información y los tipos necesarios para que los reducers lo apliquen para modificar el estado.

La principal ventaja de usar Ngrx junto con Angular es que reduce la complejidad de la arquitectura, sobre todo para aplicaciones grandes. Además de que tener todo el estado de una aplicación Angular en un solo sitio facilita mucho las labores de desarrollo.

Si quieres saber cómo aplicar ngrx a Angular te dejo un par de artículos:

Conclusiones

Hemos visto muchas formas de compartir información entre componentes, y usar una o otra forma, dependerá de cómo sea la aplicación web que quieres desarrollar.

Si no te quieres complicar, lo más común suele ser la comunicación padre hijo mediante @Input y la del hijo con el padre mediante eventos. Si necesitas que muchos componentes accedan a los mismos datos, vas a necesitar servicios. Lo más recomendable es instalar siempre la librería NgRx ya que te va a permitir de una manera muy cómoda comapartir datos entre componentes con reactividad, es decir, si desde uno de los componentes cambias el store, automáticamente los cambios se ven reflejados en todos los componentes que tiren de ese store.

» Leer más, comentarios, etc...

Una sinfonía en C#

Los memes de Javascript

noviembre 29, 2018 10:37

No pasan más de 10 minutos desde que algo ocurre en el mundo hasta que Internet se llena de memes al respecto; sin embargo últimamente se ven muchos memes sobre Javascript y la idea de este post es explicarlos, o mejor dicho, explicar por qué son incorrectos.

El meme en cuestión

En meme del que voy a hablar en esta ocasión es éste, con Patrick Star de protagonista:

 

image

 

En este caso es una burla acerca de ciertos casos con las comparaciones en Javascript, y si bien tiene algo de gracia demuestra más que nada desconocimiento del lenguaje y vamos a explicarlo.

El comparador de igualdad

El comparador de igualdad de Javascript tiene una característica interesante que se podría resumir como que “hace el mejor esfuerzo por comparar los dos términos” para explicarlo simple: el comparador convierte los dos término en caso de no ser del mismo tipo y luego hace una comparación estricta, entonces esto nos lleva a la primera parte del meme:

0 == “0”

Básicamente al usar el comparador de igualdad == Javascript detecta que los términos no son del mismo tipo y los convierte, entonces esta comparación pasa a ser verdad como podemos ver al probarlo en Nodejs

image

es por eso que si queremos una comparación estricta usamos el operador de comparación estricta ===

image

Entonces, primer caso resulto, Javascript hace exactamente lo que dice la documentación que debe hacer, una comparación con conversión de tipos.

Un array vacío

Los arrays siempre tienen sus bemoles en todos los lenguajes y Javascript no es la excepción, pero por qué  0 == [ ] ?

La respuesta es que la conversión de un array vacío a entero nos retorna el valor de una posición de memoria vacía que es comparable con 0, entonces la comparación es verdadera, una vez más si usamos el comparador estricto es no es así:

image

La comparación con un string es false

 

"0" == [] 

Acá es simple, basados en lo anterior, es evidente que podemos convertir un array vacío a entero y nos retorna algo comprable con false porque después de todo estamos pidiendo que haga un esfuerzo por convertir una posición sin valores. En este caso el término de la derecha resultará ser o o algo comparable con false, sin embargo ahora el término de la izquierda es un string que a lo sumo es comparable con un 0.

Conclusión

Un lenguaje de programación es complejo y tiene detalles, si en el día a día lo usamos debemos conocer sus características y no olvidarnos que puede ser (como en el caso de Javascript) un lenguaje diseñado hace muchos años y si estamos acostumbrados a lenguajes más modernos que han aprendido de los problemas que pueden acarrear estas cosas y que los programadores de hoy en día usan un lenguaje sin conocerlo…

 

Nos leemos.

» Leer más, comentarios, etc...

Metodologías ágiles. De lo racional a la inspiración.

Scrum master a tiempo completo: 42 Tareas

noviembre 27, 2018 06:32

Uno de los artículos que más referencio en mi curso de Scrum cuando hablo de las labores del Scrum Master es: 42-tasks-for-a-scrum-masters-job. Por alguna razón, todo el mundo parece entender que el Product Owner es un trabajo a tiempo completo, o ser miembro de un equipo también, pero que probablemente el rol del Scrum Master puede ser realizado a media jornada o incluso menos. El scrum master

» Leer más, comentarios, etc...

Variable not found

Mi controlador tiene muchos parámetros en el constructor, ¿estoy haciendo algo mal?

noviembre 27, 2018 08:02

DesarrolladoresComo sabemos, la inyección de dependencias está grabada a fuego en los genes de ASP.NET Core. La mayoría de sus componentes la usan internamente para obtener acceso a otros componentes que necesitan para cumplir sus funciones y, además, es una práctica recomendada en la parte que nos toca a nosotros como desarrolladores de aplicaciones.

Por ello, en ASP.NET Core MVC, lo habitual es que implementemos nuestros controladores atendiendo a este principio, y para ello utilicemos la técnica de inyección de dependencias en el constructor:
public class InvoiceController: Controller
{
private readonly IInvoiceServices _invoiceServices;
private readonly IMapper _mapper;
private readonly ILogger<InvoiceController> _logger;

public InvoiceController(
IInvoiceServices invoiceServices,
ILogger<InvoiceController> logger,
IMapper mapper)
{
_invoiceServices = invoiceServices;
_logger = logger;
_mapper = mapper;
}
...
}
Nota: aunque en este post estemos hablando de controladores ASP.NET Core MVC, las ideas que vamos a comentar aquí son también aplicables a ASP.NET MVC "clásico" e incluso a otro tipo de frameworks que comparten los mismos conceptos.

Controladores endiosados, o dioses controladores

Cuando el controlador crece, es probable que la lista de parámetros del constructor también aumente de forma significativa, lo cual se convierte automáticamente en un code smell (¡hola, "S" de SOLID!). Llegado este caso, probablemente estemos otorgando demasiadas responsabilidades a este controlador, y, con ello, disminuyendo su cohesión, legibilidad, mantenibilidad y robustez.

De la misma forma, podemos encontrarnos con un exceso de responsabilidades, pero no sólo desde el punto de vista numérico, sino conceptual. A veces trasladamos al controlador responsabilidades que serían más propias del modelo, y esto se refleja claramente en las dependencias que declaramos en su constructor.

Por último, también hay que pensar que no todas las acciones del controlador utilizarán todas las dependencias recibidas. Por tanto, en determinadas ocasiones estaremos instanciando un grafo de objetos excesivo para el uso real que vamos a darle en el proceso de una petición, lo cual puede tener su repercusión en términos de rendimiento y memoria, sobre todo si la estructura de dependencias es compleja. Esto podría ser especialmente doloroso si alguna de las dependencias tiene un constructor complejo o que realice tareas costosas (algo que, por otra parte, no es recomendable en absoluto), porque estaríamos sometiendo al sistema a un stress innecesario cuando la acción a ejecutar no requiera dicha dependencia para llevar a cabo su misión.

La cuestión es que, con el paso del tiempo, si no tenemos cuidado podríamos llegar a tener un controlador con aspiraciones divinas como el siguiente:
public class InvoiceController: Controller
{
private readonly IInvoiceServices _invoiceServices;
[...] // Other private members

public InvoiceController(
IInvoiceServices invoiceServices,
ICustomerServices customerServices,
ISupplierServices supplierServices,
IStockServices stockServices,
IDeliveryServices deliveryServices,
IDigitalSignatureServices digitalSignatureServices,
IAuthorizationServices authorizationServices,
IAuditServices auditServices,
IMailingService mailingServices,
ISmsServices smsServices,
IPushServices pushServices,
IPdfGenerator pdfGenerator,
IMapper mapper,
ILogger<InvoiceController> logger,
[...]
)
{
_invoiceServices = invoiceServices;
_customerServices = customerServices;
_supplierServices = supplierServices;
_stockServices = stockServices;
_deliveryServices = deliveryServices;
_digitalSignatureServices = digitalSignatureServices;
_authorizationServices = authorizationServices;
_auditServices = auditServices;
_mailingServices = mailingServices;
_smsServices = smsServices;
_pushServices = pushServices;
_pdfGenerator = pdfGenerator;
_mapper = mapper;
[...]
}
...
}

Pues sí, así son mis controladores... ¿cómo lo soluciono?

Si ya estamos en este punto, o incluso bastante antes, podemos asegurar que tenemos un problema. Lo mejor es empezar a actuar inmediatamente; cuanto más tardemos en empezar a refactorizar, más difícil será el proceso para dejarlo todo en condiciones.

Esta refactorización podría ir enfocada a las siguientes líneas de actuación:
  • Trocear el controlador en controladores más especializados, pequeños y manejables. Por ejemplo, un controlador para gestionar todo lo relativo con facturación suena demasiado ostentoso, ¿verdad? Quizás deberíamos plantearnos tener controladores específicos para la generación de facturas, otros para reportes, otro para firmas electrónicas, etc. El resultado será un diseño respetuoso con el principio SRP y clases bastante más especializadas y con muchas menos dependencias.

  • Extraer funcionalidades trasversales a filtros. Por ejemplo, en el caso anterior serían buenos candidatos los servicios de autorización o auditoría. Esto nos ayudará a tener controladores mucho más pequeños y a reutilizar bastante código, pues en muchas ocasiones estas operaciones trasversales son comunes entre acciones e incluso entre distintos controladores.

  • Mover funcionalidades al Modelo. En el ejemplo anterior lo vemos claramente: si nuestra aplicación debe enviar una notificación cada vez que se crea una factura, el envío del mail no debería realizarlo el controlador, sino la clase del Modelo que proporciona los servicios de creación de las mismas, porque este proceso forma parte de la lógica de negocio del sistema. Confundir la ubicación de determinadas funciones es uno de los principales causantes de estos controladores subidos de peso.

  • Agrupar servicios en abstracciones de nivel superior. Por ejemplo, no tiene sentido que el controlador reciba dependencias a servicios de notificaciones por email, SMS o push; quizás deberíamos plantearnos tener un INotificationSender que sea el que decida por qué vía debe notificar al usuario y encapsule las dependencias que implementan cada tipo de mensaje. En muchas ocasiones, el uso de niveles de abstracción incorrectos hacen que nuestras clases tengan que recibir más dependencias de la cuenta y aumentar la cantidad de código implementado en las mismas.

  • No estaría de más echar un vistazo a patrones como Mediator, Facade, Command, y arquitecturas tipo CQS, uso de eventosu otras técnicas que nos ayuden a simplificar nuestros componentes y las relaciones entre ellos.
En cualquier caso, salvo ocasiones muy justificadas, lo que no deberíamos hacer es ceder ante la tentación de intentar disimular el exceso de dependencias mediante la instanciación directa o el uso de service locators u otras técnicas de inyección. Aunque estas herramientas pueden resultar útiles en determinados escenarios, pueden dar lugar a problemas porque harán menos visibles las dependencias y, por tanto, menos evidentes la violación del principio de separación de responsabilidades, que al final es lo que tenemos que evitar.

Pero... ¿cuántos parámetros son demasiados parámetros en el constructor?

Pues no creo que haya un número exacto de parámetros que actúe como frontera entre lo correcto y lo incorrecto. Como suele ocurrir, depende de los escenarios: puede haber controladores en los que tres parámetros ya sean demasiado porque estemos introduciendo en él más responsabilidades de la cuenta, y puede haber otros donde recibir seis dependencias pueda resultar correcto porque sean esenciales para realizar las tareas que tenga encomendadas.

Eso sí, parece existir un cierto consenso en que a partir de cuatro parámetros debemos activar las alertas y empezar a plantearnos si es necesario refactorizar, y el nivel de probabilidad de que sea necesaria dicha refactorización irá subiendo de forma exponencial conforme aumente el número de parámetros.

Publicado en: www.variablenotfound.com.

» Leer más, comentarios, etc...

Poesía Binaria

Cómo hacer carpetas IMAP compartidas con Cyrus

noviembre 26, 2018 10:45

Un servicio de correo electrónico suele ser algo personal. Sin embargo hay ocasiones en las que nos interesa que varios usuarios sean capaces de gestionar conjuntamente un buzón de correo. Podemos pensar el caso de una empresa en la que varios empleados reciben los mensajes de una dirección de correo determinada, por ejemplo info@miembpresa.com, y que de vez en cuando el jefe viene a echar un vistazo a dichos correos. O que, por ejemplo tenemos que enviar decenas o cientos de mensajes a otro usuario dentro del mismo servidor, podemos pensar en que un empleado deja la empresa y otro compañero va a encargarse de las conversaciones que tenga actualmente con los clientes.

Es cierto que, en la práctica, muchas veces se opta por compartir las contraseñas del correo y configurar todo el mundo la misma cuenta en su programa de correo, y ahí hacer los cambios que creamos oportunos. Aunque, si queremos tener en cuenta la seguridad, una cuenta como info@miempresa.com no puede tener su contraseña volando por la oficina. En primer lugar, no sabemos si un empleado filtrará la clave o utilizará la cuenta para fines diferentes a los que pensamos. Y, si confiamos en nuestros empleados, no podemos confiar en sus ordenadores que en cualquier momento puede tener algún problema, ser víctimas de malware y pueden jugarnos una mala pasada. Si hay una filtración, no podríamos determinar con exactitud su procedencia. Por otro lado, no podríamos limitar el acceso a dicha cuenta si nos interesa que un usuario solo pueda leer los mensajes o no queremos que pueda marcar mensajes como leídos (muy útil cuando el jefe quiere supervisar mensajes sin entorpecer).

En mis servidores utilizo el software Cyrus para gestionar el correo entrante en el servidor. Con él manejo las cuentas IMAP de los usuarios y todos ellos se conectan a él para la lectura de su correo. Es aquí donde podemos hacer uso de las capacidades de buzones de correo compartidos para satisfacer nuestras necesidades.

Notas iniciales

Estas configuraciones son válidas tanto para Cyrus 2.2 como Cyrus 2.4 (lo he probado en las dos versiones). Además, yo estoy utilizando la directiva

1
unixhierarchysep: yes

en el archivo /etc/imapd.conf que habilita puntos (.) en los nombres de usuario, con lo que la separación de niveles en lugar de un punto utilizará una /. De todas formas comentaré esto donde sea necesario.

Otra cosa más, en los nombres de usuario y los buzones, a mí me gusta poner siempre el dominio al que pertenecen, aunque es incómodo de escribir siempre, me sirve como una medida de seguridad ante confusiones y me permite crear buzones en dominios diferentes, que cuando te piden un correo en un dominio temporalmente, o el dominio tiene alternativas en .es, .eu o .com es un engorro ponerlo todo bien con su dominio.

Dando de alta el buzón compartido

Para ello, tenemos que identificarnos en la administración de cyrus. En mi caso, el usuario es administrador y el host es localhost porque estoy en el mismo servidor de correo:

cyradm -u administrador localhost

Luego, tendremos que crear el buzón compartido, en mi caso (info@miempresa.com):

cm shared/info@miempresa.com

¡Cuidado! Si no tienes unixhierarchysep: yes, tendrás que utilizar un punto en lugar de una barra (compruébalo antes de hacer cambios):
cm shared.info@miempresa.com

Dando permisos al buzón

Tras ello, tenemos que dar permisos, imaginemos que tenemos dos empleados, ana@miempresa.com y bruno@miempresa.com , y por si fuera poco, tenemos a jefe@miempresa.com que quiere poder ver mensajes, pero que no se note su presencia. Cyrus establece las siguientes flags de permisos (y cuando las usas un par de veces, te las puedes aprender en este orden lrswipcda):

  • l: (lookup) Nos permite obtener este buzón en un listado.
  • r: (read) Da permiso de lectura a los contenidos del buzón.
  • s: (seen) Mantiene el estado de seen/recent entre varias sesiones IMAP. Los mensajes vistos y recibidos recientemente.
  • w: (write) Escritura de flags en los mensajes del buzón.
  • i: (insert) Permite copiar o mover mensajes aquí.
  • p: (post) Nos deja entregar un mensaje en este buzón.
  • c: (create) Crear un nuevo sub-buzón.
  • d: (delete) Nos deja borrar mensajes o el propio buzón.
  • a: (acl) Nos deja administrar el buzón, o cambiar estos permisos del ACL.

Así que, para dar permiso a los usuarios seleccionados, y adicionalmente dejar que cualquiera pueda entregar correos aquí:

sam shared/info@miempresa.com info@miempresa.com anyone p
sam shared/info@miempresa.com ana@miempresa.com lrswipcda
sam shared/info@miempresa.com bruno@miempresa.com lrswipcda
sam shared/info@miempresa.com jefe@miempresa.com lr

Podríamos utilizar setaclmailbox en lugar de sam. En este punto, ya podríamos utilizar el buzón desde los usuarios ana, bruno y jefe.

Recibir correo en el buzón compartido desde Postfix

Si queremos habilitar la entrega de correo a través de una dirección de e-mail, primero debemos establecer un usuario que se utilizará para hacer la entrega. Para ello, editamos /etc/imapd.conf de la siguiente forma (el nombre de usuario puede ser el que queramos, siempre y cuando no esté en el sistema):

1
postuser: sharedmanager

Podemos colocar dicha línea en cualquier punto del archivo, yo prefiero poner al final todas las modificaciones a ese archivo.

Ahora, debemos crear un alias de correo a un nombre de usuario del sistema. Para ello editamos /etc/postfix/valias introduciendo lo siguiente:

1
info@miempresa.com infomiempresacom

Así le decimos que info@miempresa.com corresponde con el alias infomiempresacom, que introduciremos en /etc/aliases así:

1
infomiempresacom: sharedmanager+shared/info@miempresa.com

Tras esto, dependiendo de la configuración que tengamos en postfix (el contenido debe venir precedido por “hash:” virtual_alias_maps y alias_maps en /etc/postfix/main.cf ; Nota, mis archivos se llaman /etc/aliases y /etc/postfix/valias, los tuyos tal vez sean distintos), tendremos que ejecutar lo siguiente:

sudo postalias /etc/aliases
sudo postmap /etc/postfix/valias

Con esto crearemos los archivos /etc/aliases.db y /etc/postfix/valias.db. Ya deberíamos poder recibir los e-mails enviados a info@miempresa.com en el buzón compartido por Ana y Bruno. Cada usuario utilizará su contraseña para entrar en el buzón y el jefe podrá leer todos los mails sin marcarlos como leídos, ni quitar el flag de recién recibido.

Foto principal: unsplash-logoAnnie Spratt

The post Cómo hacer carpetas IMAP compartidas con Cyrus appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 339

noviembre 26, 2018 07:56

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada, algo más escasa de lo habitual debido a los festivos en USA. Pocos, pero espero que interesantes :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros


Publicado en: www.variablenotfound.com.

» Leer más, comentarios, etc...

Blog Bitix

Flatpak, distribución e instalación de programas de escritorio en las distribuciones GNU/Linux

noviembre 23, 2018 04:30

Flatpak
Linux

Las distribuciones GNU/Linux son la recolección de cantidad de programas de software libre, cientos o miles, con una garantía de que funcionan correctamente en esa distribución y que facilita a los usuarios una fácil utilización. Hay múltiples distribuciones GNU/Linux con diferentes intereses ya sean generalistas como Ubuntu, Fedora, Debian, Arch Linux, elementary, openSUSE entre las más populares y otras especializadas en propósitos más específicos para servidor con RHEL, CentOS, Zentyal o Alpine, para equipos con pocos recursos como Puppy o Tiny Core, seguridad y privacidad como Tails o sin componentes privativos como Trisquel.

Cada una de estas distribuciones es creada por personas que se encargan de mantener la distribución cogiendo el código fuente que los programadores del software crean y publican, compilandolo para crear un binario o paquete utilizable e instalable en esa distribución y comprobar que funciona correctamente sin conflictos con otros paquetes. Los mantenedores se encargan de publicar nuevas versiones de paquetes con cada nueva versión de cada programas, de reportar errores y de incluso aplicar parches de seguridad cuando se descubren.

Esta tarea que hacen los mantenedores se hace por cada paquete, son miles los que contiene una distribución, Debian tiene más de 51000, y por cada distribución aunque algunas se basan en otras como Ubuntu se basa en Debian y otras se basan a su vez en Ubuntu. Esto es una ingente cantidad de tiempo de dedicación de muchas personas y replicado en gran parte en cada distribución que podrían ser empleado en otras tareas.

La tarea que hacen los mantenedores es útil para comprobar que los paquetes de cada distribución funcionan correctamente y en conjunto con el resto de paquetes y porque los autores originales del software no tenían una forma de distribuir su software para que funcionase correctamente en todas las distribuciones.

Flatpak se define como el futuro, aunque es ya un presente, de forma de distribuir aplicaciones de escritorio siendo varias distribuciones GNU/Linux importantes que ya lo soportan. Las ventajas que proporciona son:

  • Los paquetes de flatpak son utilizables en cualquier distribución.
  • Los autores del software pueden crear el paquete sin depender de que los mantenedores lo incluyan en una distribución para que sea utilizable por sus usuarios.
  • Una nueva versión está disponible inmediatamente para todas las distribuciones que será más una buena mejora para distribuciones con ciclos de publicación más lentos.
  • Proporciona entornos consistentes.
  • Las aplicaciones son utilizables incluso en nuevas versiones de una distribución.
  • Se pueden instalar múltiples versiones de una misma aplicación.
  • Hay menos riesgo de que una actualización de un programa provque que el sistema quede inconsistente pudiendo incluso a no llegar a iniciarse correctamente.
  • Se mejora la seguridad el ejecutarse las aplicaciones de forma aislada en una caja de arena que limita las acciones que puede realizar. Los permisos como almacenamiento, red y dispositivos se han de conceder de forma explícita.
  • Cualquiera puede publicar su aplicación y ser utilizable por los usuarios sin que necesite ganar popularidad para que los mantenedores la incluyan en la distribución.
  • Las aplicaciones Flatpak se puede instalar a nivel de sistema (por defecto) y a nivel de usuario.

La instalación de Flatpak y guía de uso no es más complicado que utilizar el propio gestor de paquetes de cada distribución. Los desarrolladores tienen su guía para crear paquetes y publicar en Flatpak.

Flathub es un repositorio de las aplicaciones Flatpak que en el ejemplo se añade con el comando con flatpak remote-add. Las que hay se pueden navegar por categoría y son algunas de las más populares y que es probable querer instalar en cualquier sistema. A medida que pase el tiempo habrá más disponibles.

  • Audio y vídeo: Audacity, GNOME Music, HandBrake, OpenShot, Pitivi, Rhythmbox, VLC, …
  • Desarrollo: Atom, gitg, Meld, Backets, Sequeler, SmartGit, Sublime Text, Visual Studio Code, …
  • Educación: GCompris, GeoGebra, GNU Octave, …
  • Juegos: 0 A.D, Battle for Wesnoth, Games, gbrainy, GNOME Chess, OpenTTD, ScummVM, Shattered Pixel Dungeon, Steam, SuperTuxKart, Teeworlds, …
  • Gráficos y fotografía: Blender, Colo , GNU Image Manipulation Program (GIMP), Image Optimizer, Inkscape, Krita, Scribus, Synfig Studio, …
  • Comunicación y noticias: Dropbox, FeedReader, FileZilla, Geary, JDownloader, Pidgin, Polari, Remmina, Signal, Skype, Slack, Telegram, Thunderbird, Transmission, …
  • Productividad: Calendar, Calibre, Contacts, Evolution, LibreOffice, Thunderbird, …
  • Ciencia: Elements, Genius, Stellarium, …
  • Configuración: RazerGenie, …
  • Utilidades: Agenda, FreeFileSync, KeePassXC, Nextcloud, Vim, …

Con las ventajas de Flatpak tanto para usuarios, desarrolladores y mantenedores y a medida que gane más popularidad los desarrolladores tendrán más motivación por publicar sus aplicaciones en paquete Flatpak y en alguno de sus repositorios.

En la página de preguntas frecuentes está el curioso origen del nombre de Flatpak en relación con una de las innovaciones de paquetes planos de IKEA. Es una tecnología acoplada a Linux ya que utiliza varias de las tecnologías propias de Linux y por tanto no está para las distribuciones BSD como ocurre en otros casos. El entorno de desarrollo GNOME Builder soporta la programación para Flatpak.

Flatpak está más apoyada por Red Hat, Canonical tiene su tecnología similar con snaps. Ha ocurrido igual en anteriores casos con systemd y Upstart o Wayland y Mir donde las tecnologías más apoyadas por Red Hat han sido las que mayor éxito han tenido y han prevalecido. Si nada cambia en Red Hat al ser adquirida por parte de IBM puede que se produzca el mismo resultado no tanto por que las tecnologías de Red Hat sean mejores sino porque tiene más peso en la comunidad que Canonical. Por el momento la adopción para snap contra la de Flatpak no produce buenos augurios para la primera donde solo en Ubuntu es buena como no podría ser de otra forma.

Si usas Arch Linux e instalas el entorno de escritorio GNOME con los paquetes gnome y gnome-extra ya tendrás instalado Flatpak ya que se instala como dependencia. Se puede instalar directamente con el gestor de paquetes pacman con el siguiente comando:

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 337

noviembre 23, 2018 07:35

Enlaces interesantes Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en: www.variablenotfound.com.

» Leer más, comentarios, etc...

Poesía Binaria

Contenedores docker de aplicaciones de escritorio [con ejemplos listos para usar]

noviembre 21, 2018 04:12

En los últimos años, han surgido varias tecnologías para compartimentar aplicaciones. Desde ejecutar una aplicación desde una jaula, hasta virtualizar un sistema operativo completo dentro de nuestro sistema. Algo que se utiliza mucho en servidores. Actualmente hay una tecnología en auge, y es Docker. Docker nos permite encerrar una aplicación, junto con las bibliotecas que necesita para funcionar y ejecutarla en un entorno aislado, de forma que la aplicación compartimentada no pueda ver nada del exterior. Aunque, si lo deseamos, podrá conectar por red con otros recursos o a Internet.

Docker se usa mucho en el ámbito de los servidores. Nos permite ejecutar aplicaciones de manera que cada una se ejecute en un espacio determinado, con unos permisos, utilizando unos recursos de sistema definidos y utilizando un sistema operativo y bibliotecas de sistema determinadas, que pueden no ser las de nuestro sistema. Otra ventaja añadida es que los contenedores docker tienen un guión definido para su construcción, lo que nos asegura que la configuración del software del contenedor no variará entre instancias, podemos perfectamente configurarlo en nuestro ordenador local e instalarlo más tarde en un servidor cuando nos aseguremos de que todo esté bien, o hacer que todo un equipo (o empresa) utilice el mismo software, en la misma versión y con la misma configuración y evitar así la típica excusa de: “En mi ordenador funciona.”

Docker en el escritorio

Pero docker compartimenta aplicaciones, da igual del tipo que sean. Así que, ¿por qué no compartimentar aplicaciones de escritorio? De forma que encerremos todo lo que necesitamos de la misma en un entorno controlado y así evitamos tener problemas con dependencias, versiones, incluso con entornos Java cuando utilicemos dicha aplicación. Es muy común que una aplicación se actualice para utilizar bibliotecas en versiones muy nuevas y nuestra distribución no instale esas dependencias, incluso cuando las instalamos a mano, podemos meter la pata y hacer que nuestro sistema se vuelva inestable o que no funcione directamente. También es muy útil cuando necesitamos software que ya no está soportado, o necesitamos una versión antigua y vamos a ejecutarla en un sistema operativo más moderno que no instala las versiones de las bibliotecas que necesitamos.

Puede parecer un gasto innecesario de disco duro, porque seguramente tengamos muchas bibliotecas y software repetido en nuestro sistema; y de memoria, porque habrá veces que la misma biblioteca esté cargada en RAM varias veces (aunque podemos optimizar un poco nuestro kernel para que no tenga mucho problema con eso). Pero nos dará seguridad en nuestro sistema, ya que esta aplicación estará aislada y no tendrá acceso a recursos sin permiso, para esa aplicación ni existirán. Y además, la posibilidad de llevarnos la aplicación a otro equipo o servidor, construirla allí y que todo esté igual que en nuestro ordenador.

Construyendo el Dockerfile. Como ejemplo dbeaver

Cada Dockerfile será independiente de la aplicación que necesitemos ejecutar. Aunque podemos utilizar este archivo como base. En este caso, vamos a dockerizar el programa dbeaver, una poderosa herramienta de administración de base de datos con muchas opciones. Como vemos, es una aplicación hecha en Java. Y, puede que en nuestro ordenador o nuestro servidor, no nos interese tener un entorno de Java públicamente disponible, y solo dejarlo para este programa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM ubuntu:18.04
MAINTAINER Gaspar Fernandez <gaspar.fernandez@totaki.com>
ARG UNAME=user
ARG UID=1000
ARG GID=1000

RUN apt-get update \
        && apt-get -y install wget \
        && apt-get -y install libxext6 libxrender1 libxtst6 libxi6 software-properties-common \
        && apt-get -y install openjdk-8-jre \
        && wget -q --output-document=/tmp/dbeaver.deb https://dbeaver.io/files/dbeaver-ce_latest_amd64.deb \
        && dpkg -i /tmp/dbeaver.deb \
        && mkdir -p /home/$UNAME \
        && echo "$UNAME:x:${UID}:${GID}:Developer,,,:/home/$UNAME:/bin/bash" >> /etc/passwd \
        && echo "$UNAME:x:${UID}:" >> /etc/group \
        && chown ${UID}:${GID} -R /home/$UNAME \
        && gpasswd -a $UNAME audio \
        && gpasswd -a $UNAME video

COPY docker-entry.sh /usr/local/bin
ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]

USER $UNAME
ENV HOME /home/$UNAME

En este caso, partimos de una versión 18.04 de Ubuntu, en la que tenemos que instalar dependencias del escritorio X y OpenDJK, seguidamente, descargar el .deb de la página oficial de dbeaver para proceder a la instalación del programa.
Seguidamente, aunque podemos continuar como root, sobre todo para los programas de escritorio, no es aconsejable y muchos de ellos se quejan al arrancar. Así que creamos un usuario (podríamos cambiarle el nombre al usuario, aunque no será tan importante, por ahora se llamará user). Luego le asignamos grupos (en este caso no es tan importante, pero hay programas con los que sí).

Generalmente, me gusta escribir un docker-entry.sh para cada aplicación a dockerizar, aunque solo sea para ejecutar el programa nada más. Porque, a veces tenemos que filtrar argumentos, ejecutar un pequeño script antes que el programa o hacer una copia de la configuración, etc. En este caso, nuestro docker-entry.sh será este:

1
2
3
4
#!/bin/bash

echo "Ejecutando dbeaver..."
dbeaver

Tras ello, aunque podríamos utilizar docker-compose, vamos a crear un script para construir el contenedor y otro para ejecutar la aplicación. Aunque la ejecución es más o menos sencilla, no tenemos por qué recordar los argumentos para ejecutar el programa. Además, así podremos crear más fácilmente accesos directos y otras utilidades derivadas.

build.sh

1
2
3
4
5
6
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

pushd $SCRIPTPATH
docker build -t gasparfm/dbeaver .
popd

Primero, almacenamos en la variable SCRIPTPATH el nombre del directorio donde se encuentra el script, me gusta utilizar esta línea por si llamamos al script desde una ruta diferente a la ruta donde está, o si hacemos un enlace simbólico al archivo. De esta forma, obtendremos en la variable la ruta exacta donde se encuentra el archivo de script al que hace referencia el enlace. Y así podremos realizar tareas en ese directorio, como por ejemplo la construcción de la imagen de nuestro contenedor.

run.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

pushd $SCRIPTPATH
docker run --rm --name dbeaver -e DISPLAY=$DISPLAY \
    -v /dev/shm:/dev/shm:rw \
    -v /etc/machine-id:/etc/machine-id:ro \
    -v /var/lib/dbus:/var/lib/dbus:ro \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -v /etc/localtime:/etc/localtime:ro \
    -v /etc/hosts:/etc/hosts:ro \
    -v $(pwd)/user:/home/user \
    -v $HOME/.ssh:/home/user/sshkeys:ro \
    gasparfm/dbeaver
popd

Para ejecutar el script, podemos crear un enlace en /usr/local/bin a este archivo, llamándolo como queramos. Aquí comento algunos argumentos utilizados:

  • –rm: Borra el contenedor si existe. Para utilizar siempre el mismo, porque normalmente solo vamos a necesitar una instancia en ejecución
  • –name dbeaver: Le damos nombre al contenedor, para que no tenga un nombre aleatorio.
  • -e DISPLAY=$DISPLAY: Asignamos la pantalla sobre la que escribirá la aplicación a la pantalla actual del usuario.
  • -v /dev/shm:/dev/shm:rw: Algunos programas necesitan acceso a este dispositivo de memoria compartida.
  • -v /etc/machine-id:/etc/machine-id:ro : Extraemos el identificador único de la máquina.
  • -v /var/lib/dbus:/var/lib/dbus:ro : En este directorio también suele almacenarse el identificador único de la máquina.
  • -v /tmp/.X11-unix:/tmp/.X11-unix : En este directorio suelen almacenarse los sockets para acceder al entorno X.
  • -v /etc/localtime:/etc/localtime:ro : Aquí reside la zona horaria de nuestro ordenador y la vinculamos con la de la aplicación dockerizada.
  • -v /etc/hosts:/etc/hosts:ro : Si tenemos hosts personalizados en /etc/hosts, nuestra aplicación debe poder acceder a ellos. O, al menos, resolverlos.
  • -v $(pwd)/user:/home/user : Vinculamos el directorio local user (tendremos que crearlo) con el /home/user de la aplicación. Sobre todo porque este programa introduce mucha información que debe ser salvada. Por ejemplo, se descarga drivers de base de datos o genera muchos archivos de configuración, etc.
  • -v $HOME/.ssh:/home/user/sshkeys:ro : Una opción de este programa es que nos permite acceder a bases de datos a través de túneles SSH. Por eso mismo, estaría bien poder acceder a nuestras claves privadas de SSH. Podríamos montar este volumen así o copiar las claves a mano al directorio user.
  • gasparfm/dbeaver : Es el nombre del contenedor

Solo tendremos que ejecutar el programa:

Dockerizando yed

Ahora vamos a incluir un segundo programa, yed. Un software para crear diagramas, hecho en Java también. Pero esta vez necesita del JRE de Oracle. Lo que ya nos da una pista de la potencia y necesidad de dockerizar este tipo de aplicaciones.

Vamos a seguir el mismo sistema, creamos Dockerfile, docker-entry.sh, build.sh y run.sh.

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
FROM ubuntu:18.04
MAINTAINER Gaspar Fernandez <gaspar.fernandez@totaki.com>
ARG VERSION=3.18.1.1
ARG UNAME=user
ARG UID=1000
ARG GID=1000

RUN apt-get update \
        && apt-get -y install wget \
        && apt-get -y install libxext6 libxrender1 libxtst6 libxi6 software-properties-common unzip
RUN add-apt-repository ppa:webupd8team/java \
        && apt-get update \
        && echo debconf shared/accepted-oracle-license-v1-1 select true | \
                debconf-set-selections \
        && echo debconf shared/accepted-oracle-license-v1-1 seen true | \
                debconf-set-selections \
        && apt-get -y install oracle-java8-installer
RUN wget -q --output-document=/tmp/yEd.zip https://www.yworks.com/resources/yed/demo/yEd-${VERSION}.zip \
        && unzip /tmp/yEd.zip -d /opt/ \
        && mkdir -p /home/$UNAME \
        && echo "$UNAME:x:${UID}:${GID}:Developer,,,:/home/$UNAME:/bin/bash" >> /etc/passwd \
        && echo "$UNAME:x:${UID}:" >> /etc/group \
        && chown ${UID}:${GID} -R /home/$UNAME \
        && gpasswd -a $UNAME audio \
        && gpasswd -a $UNAME video

COPY docker-entry.sh /usr/local/bin
ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]

USER $UNAME
ENV HOME /home/$UNAME

docker-entry.sh:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

ARGS=''
if [ $# -ge 1 ]; then
  ARGS="$HOME/$(basename $1)"
  shift 1
  ARGS="$@ $ARGS"
fi

java -jar "/opt/$(ls)/yed.jar" $ARGS

Esta vez, necesitamos que yed acepte argumentos del usuario, como por ejemplo el archivo que vamos a abrir.

build.sh

1
2
3
4
5
6
7
#!/bin/bash

readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

pushd $SCRIPTPATH
docker build -t gasparfm/yed .
popd

run.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

pushd $SCRIPTPATH
docker run --rm --name yed -e DISPLAY=$DISPLAY \
    -v /dev/shm:/dev/shm:rw \
    -v /etc/machine-id:/etc/machine-id:ro \
    -v /var/lib/dbus:/var/lib/dbus:ro \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -v /etc/localtime:/etc/localtime:ro \
    -v /etc/hosts:/etc/hosts:ro \
    -v $(pwd)/user:/home/user \
    gasparfm/yed
popd

Otras ideas: navegadores

De la misma forma podemos construir programas libres o privativos. Introduciendo una capa de seguridad en estos últimos, ya que no tendremos forma de saber lo que hacen. Incluso instalando versiones determinadas de wine para hacer funcionar algunos programas. Y como última idea, podemos dockerizar navegadores, instalaciones completas de Firefox / Chrome / Chromium en las que podremos limitar los recursos para no engullir RAM o CPU (podríamos limitarles la memoria a 1Gb, por ejemplo).

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FROM ubuntu:18.04
ARG UNAME=user
ARG UID=1000
ARG GID=1000

RUN apt-get update \
    && apt-get -y install sudo iproute2 \
    && apt-get -y install chromium-browser \
    && apt-get -y clean
RUN mkdir -p /home/$UNAME && \
    echo "$UNAME:x:${UID}:${GID}:Developer,,,:/home/$UNAME:/bin/bash" >> /etc/passwd && \
    echo "$UNAME:x:${UID}:" >> /etc/group && \
    chown ${UID}:${GID} -R /home/$UNAME \
    && gpasswd -a $UNAME audio \
    && gpasswd -a $UNAME video \
    && echo "$UNAME ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/user

COPY docker-entry.sh /usr/local/bin
RUN chmod +x /usr/local/bin/docker-entry.sh
USER $UNAME
ENV HOME /home/$UNAME

ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]

docker-entry.sh:

1
2
3
4
#!/bin/bash

# Podríamos incluir argumentos aquí.
chromium-browser

build.sh:

1
2
3
4
5
6
#!/bin/bash
readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

pushd $SCRIPTPATH
docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) --build-arg UNAME=user -t gasparfm/chromium .
popd

run.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash

readonly SCRIPTPATH="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

pushd $SCRIPTPATH

docker run -i --rm --name chromium --privileged -e DISPLAY=$DISPLAY \
    -v /dev/shm:/dev/shm:rw \
    -v /etc/machine-id:/etc/machine-id:ro \
    -v /run/user/$UID/pulse:/run/user/$UID/pulse:rw \
    -v /var/lib/dbus:/var/lib/dbus:ro \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -v $(pwd)/user:/home/user \
    --device /dev/video0 \
    --memory="
1000m" \
    --cpus 1 \
    gasparfm/chromium
popd

En este caso, lo estamos limitando su procesamiento a 1 CPU (o núcleo) y aproximadamente 1Gb de memoria.

Aplicaciones dockerizadas

Y tú, ¿qué aplicación de escritorio dockerizarías?

Actualización 7/12/2018: Arreglado un fallo en la redacción cambiado Debian por Ubuntu, que se me fue la cabeza. Gracias a David Latorre por avisar.

Foto principal: unsplash-logoandrew jay

The post Contenedores docker de aplicaciones de escritorio [con ejemplos listos para usar] appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin