viernes, 27 de marzo de 2020

Docker 101 ¡en mi maquina funciona!!!

En los últimos años todo el mundo está hablando de contenedores. Y especialmente de Docker.

Los contenedores solucionan un problema (o una disculpa) muy extendido en el mundo del desarrollo como es "En mi máquina funciona" también conocido como "works on my machine"

Una imagen docker no es como una máquina virtual, es mucho más ligera, pero es el símil más fácil para describir rápidamente lo que es. Aunque en el fondo no se parecen en nada.
Una maquina virtual simula una maquina completa, tanto su hardware, como su software. En el caso de un contenedor funcionan sobre el núcleo del sistema operativo de la maquina donde se ejecutan (host).
Por ejemplo una maquina linux puede hospedar una maquina virtual que ejecute windows, pero no puede ejecutar un contenedor basado en windows (sí, sí, hay contenedores basados en windows)

Para usar docker evidentemente lo primero que hay que hacer es instalarlo. Es bastante fácil, incluso en windows si quieres usar contenedores linux, docker
ofrece una solución basada en virtualización y en un futuro cercano lo hará sobre wsdl2.

Imágenes docker.
Evidentemente, lo primero que tienes que hacer es instalar docker
Lo segundo es elegir una imagen para comenzar a trabajar.

Prácticamente cualquier infraestructura que necesites tienes una imagen de docker lista para bajar y comenzar a usarla.
El mejor lugar para encontrar esas imagenes es en el repositorio oficial: el docker hub

En este post bajaremos una imagen con un servidor web apache, basada en una distribución de linux Alpine.  Para descargarnos la imágen lo único que tenemos que hacer es ejecutar el comando docker pull IMAGENAME y después listamos todas las imagenes instaladas en nuestra máquina con docker images así es como lo veo en mi máquina
~$ docker pull httpd:2.4-alpine
2.4-alpine: Pulling from library/httpd
Digest: sha256:06ad90574c3a152ca91ba9417bb7a8f8b5757b44d232be12037d877e9f8f68ed
Status: Image is up to date for httpd:2.4-alpine
docker.io/library/httpd:2.4-alpine
~$ docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
apache-krb5              1.0.0               af61fbda4863        2 days ago          112MB
httpd                    2.4-alpine          54b0995a6305        3 days ago          107MB
debezium/connect         0.9                 28e60fa48820        9 months ago        678MB
debezium/example-mysql   0.9                 c7585fcd90bc        9 months ago        373MB
mysql                    5.7                 7faa3c53e6d6        10 months ago       373MB
hello-world              latest              fce289e99eb9        15 months ago       1.84kB

Ejecutar una imagen
Una vez que tenemos la imagen descargada, pues la podemos ejecutar con el comando docker run. En este ejemplo, redijo el puerto 8080 de mi máquina al puerto 80 de la imagen docker que es donde el apache está funcionando.
~$ docker run -dit --name apache-test -p 8080:80 httpd:2.4-alpine
fea7823d78ab3b2f96f002b8524692a3037a18c76cd1ce8f088c9b12aa8a4ab5
~$ curl localhost:8080
<html><body><h1>It works!</h1></body></html>
Me puede interesar abrir una shell en un contenedor docker. Alpine no trae bash por defecto pero puedes usar ash, por ejemplo:
~$ docker exec -it apache-test /bin/ash
/usr/local/apache2 # ls
bin      build    cgi-bin  conf     error    htdocs   icons    include  logs     modules
/usr/local/apache2 # curl localhost
/bin/ash: curl: not found
/usr/local/apache2 # exit

Dockerfile: cocinando imagenes
Como se puede ver en la ejecución anterior, la imagen que nos hemos bajado no tiene curl. Para añadirlo, la manera más fácil es usando el gestor de paquetes de la distribución. Las imagenes docker son como los ogros las cebollas, es una capa, sobre otra capa, sobre ortra... Cocinar nuestra version de una imagen crearemos un fichero llamado dockerfile y partimos sobre la imagen que queramos y le vamos añadiendo lo que queramos ficheros, paquetes, etc... En este caso como mencionamos antes vamos a añadir un curl al alpine y personalizaremos el sitio web que sirve el apache, así es como quedará el docker file:
FROM httpd:2.4-alpine RUN apk update && \ apk upgrade && \ apk add curl COPY ./www /usr/local/apache2/htdocs
con el comando RUN actualizamos el repositrio de paquetes y añadimos curl, con COPY copiaremos el contenido del directorio www en el directorio htdocs de la imagen Ahora solo tenemos que crear nuestra imagen con docker build y comprobar que la tenemos intalada con docker images
~/develop/docker-images/httpd$ docker build -t nocompila-httpd:1.0.0 .
Sending build context to Docker daemon   38.4kB
Step 1/3 : FROM httpd:2.4-alpine
 ---> 54b0995a6305
Step 2/3 : RUN apk update &&     apk upgrade &&     apk add curl
 ---> Using cache
 ---> 641365964ff4
Step 3/3 : COPY ./www /usr/local/apache2/htdocs
 ---> Using cache
 ---> b36d5ae4140f
Successfully built b36d5ae4140f
Successfully tagged nocompila-httpd:1.0.0

~/develop/docker-images/httpd$ docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
nocmopila-httpd          1.0.0               b36d5ae4140f        2 minutes ago      110MB
apache-krb5              1.0.0               af61fbda4863        2 days ago          112MB
httpd                    2.4-alpine          54b0995a6305        3 days ago          107MB
debezium/connect         0.9                 28e60fa48820        9 months ago        678MB
debezium/example-mysql   0.9                 c7585fcd90bc        9 months ago        373MB
mysql                    5.7                 7faa3c53e6d6        10 months ago       373MB
hello-world              latest              fce289e99eb9        15 months ago       1.84kB

Antes de seguir como tenemos un contendor apache-test levantado y escuchando en el puerto 80 lo paramos y lo borramos con docker stop y docker rm
Ahora ya podemos lanzar nuestro nuevo container, levantamos una consola y comprobamos que los ficheros se copiaron y que tenemos curl en la caja linux:
~/develop/docker-images/httpd$ docker run -dit --name nocompila-test -p 8080:80 nocompila-httpd:1.0.0
eaf1d4c50ec557d0292250f36e0d0984b7acfa934d001ef1bea558be89302312
~/develop/docker-images/httpd$ docker exec -it nocompila-test /bin/ash
/usr/local/apache2 # ls htdocs/
index.html            worksonmymachine.png
/usr/local/apache2 # curl localhost
<html><body><h1>It works!</h1><img src="./worksonmymachine.png"></body></html>

Esto ha sido una muy pequeña introduccón, no hemos hablado de securización, despliegues, composicion, orquestación...

sábado, 22 de febrero de 2020

Back to the console... git lg

Una cosa que hecho en falta cada vez que instalo git en una máquina nueva es ver las ramas de los proyectos de una manera cómoda y entendible.
Por defecto si sobre un repositoio le haces git log verás una cosa como esta
$ git log
commit a699088fa10ade490fcba3df377e0a400dd36978 (HEAD -> master, origin/master)
Merge: 8ccc924 bf28f20
Author: Manel <usuario@correo.com>
Date:   Thu Feb 20 23:50:57 2020 +0000

    Merge pull request #1 from Manelon/develop
    
    First post

commit bf28f204ef3dc7199fee927f4ccd4f3548f5b62c (origin/develop, develop)
Author: Manel <usuario@correo.com>
Date:   Thu Feb 20 23:47:50 2020 +0000

    First post

commit 8ccc924f4b49096c9cdbfa383853bb3ca541a5c2
Author: Manel <usuario@correo.com>
Date:   Wed Feb 19 23:55:09 2020 +0000

    Set theme jekyll-theme-hacker

commit cd413d42fbd2315dbaa20174cff1cb71fef840f0
Author: Manel <usuario@correo.com>
Date:   Wed Feb 19 23:50:27 2020 +0000

    WIP
Pero la salida del comando log por defecto es... poco práctica..., vamos que se puede mejorar.
En vez de escapar a un cliente gráfico, se puede ver me una manera mucho mejor por la consola. Por ejemplo:
$ git lg
*   a699088 - (23 hours ago) Merge pull request #1 from Manelon/develop- Manel (HEAD -> master, origin/master)
|\  
| * bf28f20 - (23 hours ago) First post- Manel (origin/develop, develop)
|/  
* 8ccc924 - (2 days ago) Set theme jekyll-theme-hacker- Manel
* cd413d4 - (2 days ago) WIP- Manel

Esta opción es más compacta, informativa y además se ve claramente las ramas. Aunque git lg no está disponible por defecto, puedes añadirlo facilmente a tu consola.
Hay un montón de post en internet donde te explican como hacer esto. Yo uso la respuesta de Slipp Thompson en stack overflow y que se recojen en este post.

Lo único que tienes que hacer es añadir estos alias fichero .gitconfig que encontrarás en tu home
[alias]
    lg = !"git lg1"
    lg1 = !"git lg1-specific --all"
    lg2 = !"git lg2-specific --all"
    lg3 = !"git lg3-specific --all"

    lg1-specific = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)'
    lg2-specific = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'
    lg3-specific = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset) %C(bold cyan)(committed: %cD)%C(reset) %C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset)%n''          %C(dim white)- %an <%ae> %C(reset) %C(dim white)(committer: %cn <%ce>)%C(reset)'

Con esto tendrás tres git lgs, cada uno con más detallado que el anterior, aunque usarás git lg que por defecto apunta al menos verboso. Muy recomendable que lo tengas en tu configuración.

lunes, 17 de febrero de 2020

Back to the console... for

Mi primer ordenador no tenía ventanas, era un MSX y tenía que escribir comandos de consola en Basic.
Tras unos años llegó el flamante 386 con su MS DOS, donde tenía que pelearme con el autoexec.bat y con el consig.sys para tunear el arranque del ordenador para jugar según a qué juegos.

Pasados los años me acomodé y prefería disfrutar de ventanas de configuración todas las opciones en pantalla, botones para ejecutar acciones, etc. Vamos, no  comprendía por qué en los sistemas modernos los administradores tenía que aprenderme los parámetros de configuración, cuando en un menú bien organizado deberían aparecer todas las opciones posibles.

Los últimos años he vuelto a la consola.
Modas a parte, si quieres ser rápido y eficiente no puedo depender de los interfaces gráficos, además, aunque la curva de aprendizaje pueda ser un poco más elevada (no mucho más). Al final compensa, por velocidad, eficiencia y conocimiento de las plataformas. La mayor parte de las veces acabas pudiendo hacer más cosas, o al menos puedes automatizarlas más fácilmente.
Vamos, que los grep, cat o curl se han convertido en compañeros del día a día.

Así que permitirme que inicie una serie de posts dedicados a mi yo del futuro por si alguna vez vuelvo a las ventanas, tener un lugar donde estén algunos de los recursos que por muy básicos que parezcan me han resultado muy útiles. 

Uno de los comandos más que más estando usando últimamente es el for.
Es súper útil, puedes hacer un bucle con cualquier tipo de colección:
~$ for i in Pedro Jorge Manel; do echo hola $i; done
hola Pedro
hola Jorge
hola Manel
Puedes iterar una secuencia de números
~$ for i in {1..3}; do echo Hola $i; done
Hola 1
Hola 2
Hola 3
También puedes iterar sobre la salida de un comando de consola
:~$ for i in $(ls); do echo Hola $i; done
Hola Desktop
Hola Develop
Hola Documents
Hola Downloads
Un caso real que uso mucho últimamente es el comprobar el estado de las aplicaciones en Openshift (el Kubernetes vitaminado de Red Hat) con esta simple línea
:~$ for pod in $(oc get pods | grep nombre_deployment | awk '{print $1}'); do echo $pod && oc rsh $pod curl localhost/health && oc rsh $pod jstat -gc 1; done
Con este comando imprimo el nombre del pod, la salida del endpoint de health check y por último imprimo las estadísticas del recolector de basura de java


martes, 29 de noviembre de 2016

Probando Aplicaciones Windows con WinAppDriver

La conferencia de desarrolladores de Microsoft de este año, la Connect(); //2016 ha estado llena de anuncios como la entrada de Microsoft en la Funcación Linux como miembro platino, la unión de Google y Samsung al grupo de dirección técnica de .Net Foundation, o la presentación del Visual Studio para Mac lo que más me llamó la atención fue una pequeña demo donde probaban aplicaciones de Windows.

Los más veteranos del lugar recuerdan Selienum, se utilizaba para hacer pruebas de interfaz de usuario web, elegías el navegador desde el que se quería probar y se ejecutaba la pruebas. Pues WinAppDriver utiliza Appium, una herramienta inspirada en Selenium que monta un servidor web  que es quien realmente ejecuta la aplicación y que expone un API Web con el que nos podemos comunicar. De tal manera que podemos usarlo con prácticamente cualquier tecnología (.Net, Java, etc)

En este post voy a utilizar xUnit y voy a hacer una prueba contra el notepad.
Antes de abrir el Visual Studio hay que instalar WinAppDriver, que se encuentra en  https://github.com/Microsoft/WinAppDriver/releases.
Una vez listo, creo un proyecto en Visual Studio de librería y añado la dependencia a xUnit y otra a Appium.

Para el test quiero probar que si se pulsan las teclas aparecen en pantalla. Lo primero que tengo que hacer en el test configurar appium y decirle que inicie la aplicación de notepad. Esto lo hago asi:

	//Ejecutar notepad
	DesiredCapabilities appCapabilities = new DesiredCapabilities();
	appCapabilities.SetCapability("app", @"C:\Windows\System32\notepad.exe");
	var NotepadSession = new IOSDriver(new Uri("http://127.0.0.1:4723"), appCapabilities);

¿Pero hacemos referencia a la zona editable del Notepadd. En Selenium se hacía referencia al DOM del HTML y teníamos métodos para identificar controles por nombre o id, aquí también los tenemos ¿pero como sabemos los nombres de los ids? La respuesta es inspect.exe que viene con el Windows SDK. Si tienes instalado el Visual Studio 2015 posiblemente lo tengas instalado ya que se instala por defecto y lo puedes encontrar en C:\Program Files (x86)\Windows Kits\10\bin\x86. Ejecutamos el notepad y después el inspect y veremos como cuando pulsamos en cualquier elemento del la pantalla, en el inspect aparecen las propiedades de dicho elemento. Por ejemplo si pulsamos en la zona editable del documento del notepadd aparecerá esto: image

Si nos fijamos vemos que nos muestra como ClassName un “Edit” asi que vamos buscar ese elemento por ClassName. Asi que hacemos las acciones que queremos realizar en el test y anotamos los controles y como los podemos identificar. Por ejemplo el test que hice quedó así:

    public class WindowsAppsTest
    {
        [Fact]
        public void LaunchNotepad()
        {
            const string texto = "SHAZAM!";
            //Ejecutar notepad
            DesiredCapabilities appCapabilities = new DesiredCapabilities();
            appCapabilities.SetCapability("app", @"C:\Windows\System32\notepad.exe");
            var NotepadSession = new IOSDriver(new Uri("http://127.0.0.1:4723"), appCapabilities);
            
            //Escribir algo 
            IOSElement documentoNotepad = NotepadSession.FindElementByClassName("Edit");
            documentoNotepad.SendKeys(texto);
            Assert.Equal(texto, documentoNotepad.Text);

            NotepadSession.FindElementByName("Cerrar").Click();

            NotepadSession.FindElementByName("No guardar").Click();
        }
        
    }

  • Abrimos la aplicación, identificamos la zona del documento en notepad con FindElementByClassName("Edit")
  • después escribimos el texto de prueba
  • comprobamos que el texto que enviamos está en pantalla con Assert.Equal(texto, documentoNotepad.Text);
  • hacemos click en el boton cerrar
  • hacemos click en el botón no guardar


Ya tenemos nuestro test listo, ahora lo que tenemos que hacer antes de lanzar nuestro test es iniciar nuestro servidor de appium, que arrancamos ejecutando WinAppDriver.exe que instalamos anteriormente y ya estamos listos para lanzar la prueba.
Es un poco trabajoso tener que hacer la prueba “manual” e ir anotando los controles involucrados buscar como se identifican, pero parece un conocimiento que los expertos en pruebas deberían tener. Si trabajáis en una gran empresa no estaría mal que hubiese un departamento de testing y sería ese departamento el encargado de diseñar y programar estas pruebas. Como es sabido, no es bueno que los propios desarrolladores prueben sus desarrollos. No porque no deben de hacer (que deben) si no porque están viciados con su perspectiva.
Os dejo un par de enlaces de la documentación oficial con pruebas en java y en .net

¿Qué opináis? ¿merece la pena?

Despues de leer el post y antes de publicarlo, decidí hacer un video para mostrar la ejecución del test que programamos

domingo, 27 de noviembre de 2016

Spring Boot Rules!!!

imageHace unos meses cuando publicamos el hola mundo de Asp.Net Core Pedro me comentó que era muy parecido a Spring Boot.
Recientemente hemos publicado un hola mundo en Java con Maven, asi que como continuación vamos a hacer un hola mundo con una aplicación web utilizando Sping Boot.

Antes de empezar y para los que no lo conozcan ¿qué es Spring y qué es Spring Boot?
Cuando comencé a oír de Spring se utilizaba como inyección de dependencias, alrededor de este fueron creciendo utilidades y diversificando las técnicas utilizadas (programación orientada a aspectos, cachés, transaccionalidad, etc) con el tiempo la comunidad fue madurando y ofreciendo frameworks para desarrollar aplicaciones web, servicios, etc. Sping Boot es una forma concreta de hacer aplicaciones web, con todo el soporte de Spring basado en convenciones y reduciendo en la medida de lo posible las configuraciones (Convention over configuration)

Hagamos una aplicación web. Aunque Spring Boot ofrece un inicializador de proyectos en su web, lo vamos a hacer a mano, así que empezaremos por el POM, donde incluireos el proyecto padre spring-boot-starter-web, la dependencia con spring-boot-starter-web y el plugin spring-boot-maven-plugin

<?xml version="1.0" encoding="UTF-8"?>
<project 
    xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.nocompila</groupId>
    <artifactId>spring-boot-rules</artifactId>
    <version>0.1.0</version>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.2.RELEASE</version>
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

A diferencia del proyecto anterior, no hemos configurado el tipo de empaquetado, ni un plugin con la clase de contiene el public static void main. El plugin Spring Boot Maven ya se encarga de todo eso sin configurarlo, creando un jar que contiene todos los jars encontrados en el classpath y marcando el jar como ejecutable detectando.

Los siguiente que haremos es crear el directorio del código md src\main\java\rules directorio donde crearemos la siguiente clase:

package rules;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;

@EnableAutoConfiguration
public class SpringBootRules{
    public static void main (String [] args) throws Exception{
        SpringApplication.run(SpringBootRules.class, args);
    }
}

Con la anotación @EnableAutoConfiguration le decimos a Spring que configure la aplicación, como detecta la dependencia spring-boot-starter-web asume que estamos haciendo una aplicación web y configura un Tomcat y Spring MVC. Lo cierto es que al añadir spring-boot-starter-web se añaden muchas dependencias de las que no somos conscientes. Si ejecutamos mvn dependency:tree veremos que tenemos todas estas dependencias:

[INFO] com.nocompila:spring-boot-rules:jar:0.1.0
[INFO] \- org.springframework.boot:spring-boot-starter-web:jar:1.4.2.RELEASE:compile
[INFO]    +- org.springframework.boot:spring-boot-starter:jar:1.4.2.RELEASE:compile
[INFO]    |  +- org.springframework.boot:spring-boot:jar:1.4.2.RELEASE:compile
[INFO]    |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.4.2.RELEASE:compile
[INFO]    |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.4.2.RELEASE:compile
[INFO]    |  |  +- ch.qos.logback:logback-classic:jar:1.1.7:compile
[INFO]    |  |  |  +- ch.qos.logback:logback-core:jar:1.1.7:compile
[INFO]    |  |  |  \- org.slf4j:slf4j-api:jar:1.7.21:compile
[INFO]    |  |  +- org.slf4j:jcl-over-slf4j:jar:1.7.21:compile
[INFO]    |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.21:compile
[INFO]    |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.21:compile
[INFO]    |  +- org.springframework:spring-core:jar:4.3.4.RELEASE:compile
[INFO]    |  \- org.yaml:snakeyaml:jar:1.17:runtime
[INFO]    +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.4.2.RELEASE:compile
[INFO]    |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.5.6:compile
[INFO]    |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.5.6:compile
[INFO]    |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.5.6:compile
[INFO]    +- org.hibernate:hibernate-validator:jar:5.2.4.Final:compile
[INFO]    |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO]    |  +- org.jboss.logging:jboss-logging:jar:3.3.0.Final:compile
[INFO]    |  \- com.fasterxml:classmate:jar:1.3.3:compile
[INFO]    +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.4:compile
[INFO]    |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.4:compile
[INFO]    |  \- com.fasterxml.jackson.core:jackson-core:jar:2.8.4:compile
[INFO]    +- org.springframework:spring-web:jar:4.3.4.RELEASE:compile
[INFO]    |  +- org.springframework:spring-aop:jar:4.3.4.RELEASE:compile
[INFO]    |  +- org.springframework:spring-beans:jar:4.3.4.RELEASE:compile
[INFO]    |  \- org.springframework:spring-context:jar:4.3.4.RELEASE:compile
[INFO]    \- org.springframework:spring-webmvc:jar:4.3.4.RELEASE:compile
[INFO]       \- org.springframework:spring-expression:jar:4.3.4.RELEASE:compile

Esta vez para compilar y ejecutar vamos a utilizar mvn spring-boot:run. Podemos hacer eso porque hemos añadido como padre de la aplicación spring-boot-starter-parent en el POM. Al arrancar la aplicación veremos que al iniciar el programa se inicia un Tomcat en el puerto 8080 de nuestra máquina. Pero si pedimos una página nos dará un error el siguiente error porque no tenemos nada para responder:
image

Lo único que nos falta por hacer es poner una respuesta en algún sitio, como este es un primer ejemplo, vamos a intentar hacer todo lo más pequeño posible, así que lo vamos a hacer todo en la misma clase para que se vea bien (pero no lo hagáis en vuestro código de producción)

package rules;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class SpringBootRules{
    public static void main (String [] args) throws Exception{
        SpringApplication.run(SpringBootRules.class, args);
    }

    @RequestMapping("/**")
    String allSite (){
        return "Spring Boot Rules!!!";
    }
}

Como hemos dicho anteriormente, Sping Boot nos ha añadido SpringMVC, así que vamos a utilizar este framework. Marcamos la clase no la anotación @RestController (una meta anotación compuesta por @Controller y @ResponseBod) y añadimos un método que devuelve un String anotado con @RequestMapping(“/**”) con lo que le estamos diciendo a Spring que todas las peticiones web serán respondidas por este método.
Si volvemos a ejecutar la aplicación ahora y pedimos una página tendremos una respuesta mejor:
image

Como mencionamos anteriormente, al usar spring-boot-maven-plugin, no tenemos que configurar nada, y en el directorio target tendremos dos ficheros un jar súpervitaminado (“uber-jar”) que contiene todas las dependencias del classpath y otro finalizado jar.original que no contiene las dependencias.
El .jar lo podemos ejecutar con java –jar, por ejemplo desde el directorio raíz con java -jar target\spring-boot-rules-0.1.0.jar

En resumen, hemos hecho una aplicación de consola con un public static void main que arranca una aplicación web iniciando un Tomcat… ¿No suena esto parecido al hola mundo que hicimos con Asp.Net Core? ¿Vosotros qué creéis?
Ahh se me olvidaba, el código completo lo tenéis en GitHub

viernes, 11 de noviembre de 2016

Java Maven: Hello World

No solo de .Net vive el blog, así que tal como estamos haciendo tutoriales básicos sobre .Net Core y Asp.Net Core, vamos a hacer pequeños post introductorios al mundo Java.
Al igual que hacemos con los de .Net evitaremos en la medida de lo posible usar IDEs y utilizar editores de texto para comprender mejor lo que sucede entre bambalinas.

Así que empezaremos con lo básico, el típico hola mundo de Java con Maven. Para los desarrolladores .net que no sepan que es Maven decirles que es una herramienta que sirve para compilar y empaquetar (una especie de MsBuild) unido a un gestor de paquetes (como NuGet) y que es extensible mediante plugins, creación de distintos tipos de proyectos mediante arquetipos, etc. Lo mejor es echarle un vistazo al sitio del proyecto y ver sus características.

Maven se basa en una serie de convenciones (que se pueden cambiar mediante configuración).
La primera convención de Maven es la estructura de directorios del proyecto: El código fuente del proyecto se encuentra en src/main/java/ Así que comenzamos el proyecto creando directorios: md src\main\java\hello.

Lo siguiente que vamos a hacer es crear un fichero POM, que es el fichero de configuración de Maven. En este fichero se configuran las dependencias, los empaquetados, plugins, etc. Un fichero POM sencillo sería así:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion> <!-- Versión del POM-->
    <groupId>hello</groupId><!--Grupo al que pertenece el proyeto, como el namespace del proyecto-->
    <artifactId>helloWorld</artifactId><!--Nombre que va a recibir el empaquetado del proyecto-->
    <packaging>jar</packaging><!--Tipo de empaquetado, en este casjo JAR-->
    <version>0.1.0</version><!-- Versión del proyecto-->
</project>

Ahora vamos al código, entramos en el directorio hello y creamos un fichero con el típico hola mundo de java:

package hello;

public class HelloWorld {
    public static void main (String [] args){
        System.out.println("Hello World!!");
    }
}

Para compilar, solo tenemos que escribir mvn compile (nos olvidamos del javac) compila el proyecto. Maven deja la salida de la compilación en el directorio target\classes. Así que para ejecutar entramos en ese directorio y escribimos java hello.HelloWorld
Para crear un jar solo tenemos que ejecutar desde el directorio raíz del proyecto mvn package y tendremos en el directorio target el fichero helloWorld-0.1.0.jar (aircraftId-version)
Si intentamos ejecutar el jar nos dará un error:

C:\Desarrollo\HolaMundo>java -jar target/helloWorld-0.1.0.jar
no hay ningún atributo de manifiesto principal en target/helloWorld-0.1.0.jar

Para poder generar un jar ejecutable con Maven podemos usar el plugin Maven Shade. Esto se hace añadiendo el plugin en el POM

<build>
 <plugins>
  <plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-shade-plugin</artifactId>
   <version>2.1</version>
   <executions>
    <execution>
     <phase>package</phase>
     <goals>
      <goal>shade</goal>
     </goals>
     <configuration>
      <transformers>
       <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
        <mainClass>hello.HelloWorld</mainClass>
       </transformer>
      </transformers>
     </configuration>
    </execution>
   </executions>
  </plugin>
 </plugins>
</build>

En el plugin debemos indicar la clase que contiene el método public void main. Si ahora repetimos el empaquetado y la ejecución veremos que ya funciona.
Pero veamos como se añade una referencia, por ejemplo noda joda time (en java SE 8 ya no se recomienda usar joda, pero es un ejemplo de uso de una dependencia)

<dependencies>
 <dependency>
  <groupId>joda-time</groupId>
  <artifactId>joda-time</artifactId>
  <version>2.9.6</version>
 </dependency>
</dependencies>

Y ya con esto podemos utilizar la librería en nuestro código, con lo que nuestra clase HelloWorld quedaría así:

package hello;

import org.joda.time.LocalTime;

public class HelloWorld {
    public static void main (String [] args){
        LocalTime now = new LocalTime();
        System.out.println("Now the local time is " + now);
        System.out.println("Hello World!!");
    }
}

Con esto finalizamos el hola mundo de un proyecto java con Maven, donde hemos compilado, añadido dependencias, añadido un plugin de Maven y empaquetado el código en un jar ejecutable. El código fuente lo tenéis disponible en GitHub.
No se si el acercamiento de este tema ha sido condicionado por mi experiencia con .net. Se agradecen comentarios al respecto.

Actualización: 19/11/2016

Pedro me comenta para hacer un Jar ejecutable se suele utilizar el assembly-plugin. Elimino el plugin anterior y añado este. Y lo primero que descubro al revisar la documentación en maven es que no es necesario que ponga el groupId del plugin (ni en este caso, ni en el anterior) ya que por defecto los plugins pertenecen al grupo org.apache.maven.plugins. La sección de plugisn del POM queda ahora, tal que así:

        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>hello.HelloWorld</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <!-- this is used for inheritance merges -->
                        <phase>package</phase>
                        <!-- bind to the packaging phase -->
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>

La configuración de este plugin es diferente al anterior, seguimos teniendo que indicar la clase que tiene el public static void main (línea 11), pero además hay que indicar un Assemblie Descriptor que es lo que utiliza este plugin para crear los paquetes que se indican dentro del nodo de configuración.
Afortunadamente el plugin cuenta con algunos predefinidos, en este caso es lo que vamos a usar (jar-with-dependencies).
Ahora, al ejecutar mvn package tendremos dos jars de salida: helloWorld-0.1.0.jar y helloWorld-0.1.0-jar-with-dependencies. El ejecutable es el segundo.
Y como comentábamos anteriormente el código está disponible en GitHub.

martes, 25 de octubre de 2016

Listando los grupos a los que pertenece tu usuario

El otro día en la oficina no podía conectarme a una base de datos. La conexión era integrada de Windows. Un compañero nuevo en la oficina sí que tenía permisos, pero no sabía a qué grupos pertenecía.

En vez de liarnos con ventanas buscando los grupos, le pasé una sentencia para ejecutar en la consola y ver qué grupos ¿qué mejor que una sentencia por consola para ver a qué grupos que contienen “SQL” perteneces? así que le mandé el siguiente comando:

whoami -groups | find /i "SQL"

De esta manera vimos los grupos candidatos a los que debería pedir que incluyera mi usuario el administrados. Aunque no se use mucho, algunas veces whoami es muy útil

image

sábado, 22 de octubre de 2016

Copiar al portapapeles la salida de un comando en la consola cmd

Esta opción es muy útil y casi nadie la sabe. Cuando quieres copiar la salida de un comando al portapapeles en la consola de Windows lo único que hay que hacer es poner es “| clip”. Lo mejor es ir a la documentación (ejecuto clip /?| clip y pego en el post )

CLIP

Descripción:
    Redirecciona el resultado de las herramientas de la línea de comandos
    al Portapapeles de Windows. Esta salida de texto se puede pegar en
    otros programas.

Lista de parámetros:
    /?                  Muestra este mensaje de ayuda.

Ejemplos:
    DIR | CLIP          Coloca un copia de la lista de directorios actual
                        en el Portapapeles de Windows.

    CLIP < README.TXT   Coloca una copia del texto de readme.txt
                        en el Portapapeles de Windows.

imageLo cierto es que esto es muy cómodo y lo puedes mezclar con filtros (find o findstr). Por ejemplo: quiero sacar todas las conexiones abiertas ahora mismo en el pc al portapapeles: netstat -ano | find /i "Established" |clip

lunes, 3 de octubre de 2016

Diferencia entre Git init y Git init --bare

Hace años ya que la guerra entre Git y Mercurial terminó y con el éxito de Github, este software de control de versiones se ha convertido en el más usado. Descentralizado y sin la necesidad de ejecutar ningún software en el servidor es muy cómodo para compartir código.

Si por el motivo que sea necesitar compartir código por el sistema de ficheros, nunca crees el repositorio central con Git init a secas, porque podrás hacer un clone del repositorio, pero cuando en intentes hacer un push aparecerá un mensaje como este:

remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error: 
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error: 
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.

Aunque como indica el mensaje, puedes saltare la advertencia configurando receive.denyCurrentBranch, lo recomendado es utilizar un repositorio bare.

El respositorio desnudo

Cuando hacemos Git init en un directorio se crea un directorio oculto .git que es donde se registran los cambios (commits, ramas, etiquetas, ...). Mientras que cuando hacemos Git init --bare, no se crea un directorio de trabajo, es decir, el directorio donde se ejecuta el comando contiene lo mismo que en un repositorio normal estaría en el directorio .git. Así que el repositorio que se vaya a utilizar como repositorio central siempre se debería crear con Git init –bare.

Lo cierto es que esta es una forma muy cómoda y rápida para compartir código con compañeros, tan solo hay que elegir un directorio que esté accesible por ellos, ya sea una carpeta compartida, una ruta de red o un una carpeta de dropbox (o similar). Todo simple, incluso la seguridad, ya que es a nivel de ficheros. Así que si pueden leer el directorio, pueden hacer clone, y si pueden escribir podrán hacer push.

domingo, 18 de septiembre de 2016

Asp.Net Core: Sirviendo ficheros estáticos

Anteriormente en NoCompila hemos visto como se gestiona una petición web mediante middlewares encadenados en Asp.Net Core.
Si quisiésemos devolver el contenido de un fichero, tendríamos que escribir un middleware que analizase el httpRequest, viese qué fichero se está demandando, buscarlo en el sistema de ficheros local y servirlo.
Afortunadamente eso no hace falta programarlo, uno de los middlewares que trae de serie Asp.Net Core es UseStaticFiles que por defecto sirve los ficheros que están ubicados en el directorio wwwroot de la aplicación web.
Para que el Host de la aplicación sepa la ruta local que está manejando debemos indicárselo

public static void Main(string[] args)
{
	var host = new WebHostBuilder()
		.UseKestrel()
		.UseContentRoot(Directory.GetCurrentDirectory())
		.UseIISIntegration()
		.UseStartup<Startup>()
		.Build();

	host.Run();
}

En la línea 5 se establece el ContentRootPath en el webHost, que indica la dirección en el sistema de ficheros local donde se está la aplicación. Una vez establecido el ContentRootPath, otra propiedad se estable por defecto: el WebRootPath, que es el directorio local desde el que se van a servir los ficheros estáticos (por defecto el directorio wwwroot).
Una vez que nuestro Host conoce la ruta de los ficheros, configuramos el pipeline para que sirva ficheros estáticos.

public void Configure(IApplicationBuilder app)
{
	app.UseStaticFiles();
}

UseStaticFiles es el middleware para servir los ficheros estáticos. Pero importante, una de las carencias de Asp.Net Core es que no se puede securizar el contenido de el ContentWebRoot, es decir, de los ficheros estáticos del directorio wwwroot. Todo lo contenido en ese directorio es público sí o sí.

Sirviendo ficheros por defecto

Para servir ficheros por defecto, se utiliza otro middleware del framework: UseDefaultFiles. Este componente no sirve ficheros, lo que hace es un URL rewrite (cambia la ruta de la petición) buscando un fichero cuando solo se le indica un directorio. Si tenemos en cuenta el orden de ejecución de los middlewares importa debemos sabes que hay que usar UseDefaultFiles antes que UseStaticFiles.

public void Configure(IApplicationBuilder app)
{
	app.UseDefaultFiles();
	app.UseStaticFiles();
}

Por defecto los ficheros que busca son:

  1. default.htm
  2. default.html
  3. index.htm
  4. index.html

Habilitando la exploración de directorios

La exploración de directorios está deshabilitada por defecto por motivos de seguridad, pero si se quiere habilitar hay que hacer dos cosas:

  1. Usar el método AddDirectoryBrowser que registra los servicios de exploración de directorios en el contenedor IoC
  2. Añadir al pipeline de la aplicación el middelware UseDirectoryBrowser
public class Startup
{
	// This method gets called by the runtime. Use this method to add services to the container.
	// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddDirectoryBrowser();
	}

	// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
	public void Configure(IApplicationBuilder app)
	{
		app.UseStaticFiles();
		app.UseDirectoryBrowser();
	}
}

Sirviendo ficheros estáticos con ficheros por defecto y habilitando la exploración de directorios a la vez

Alguien debió pensar que para configurar una aplicación web que únicamente sirve ficheros no se debería tener que escribir tanto, asi que tenemos un método extensor que nos permite delegar la configuración de todos los middlewares en una única línea: UseFileServer

public class Startup
{
	// This method gets called by the runtime. Use this method to add services to the container.
	// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddDirectoryBrowser();
	}

	// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
	public void Configure(IApplicationBuilder app)
	{
		app.UseFileServer(enableDirectoryBrowsing: true);
	}
}