Automatizacion: Diario de Lecherito

Lecherito

#30 Exacto, y luego ya con esa capacidad poder hacer cosas mas guays, la cosa es que me vi creando miniproyectos demasiadas veces y al final tardaba demasiado en empezar, o se me olvidaba alguna cosa y perdia tiempo, todas estas mierdas a mi me ponen nervioso asi que decidi hacer algo al respecto.

Lecherito

Pues perfecto, ya tengo manera de generar los ejecutables para linux y mac e instalarlo por brew de una manera sencilla!

➜  randomxd brew reinstall ocean
==> Downloading http://s3.HOSTNAME/releases/ocean/ocean-0.2.x86_64_linux.bottle.tar.gz
######################################################################## 100.0%
==> Reinstalling lecherito/tools/ocean
==> Pouring ocean-0.2.x86_64_linux.bottle.tar.gz
🍺  /home/linuxbrew/.linuxbrew/Cellar/ocean/0.2: 4 files, 2MB

Luego en el futuro (no por ahora), cambiare los hardcodes dentro del codigo por otros ficheros de configuracion, pero por ahora me sirve, y ahora seguire mi caminito.

Para esto me ha tocado crear un contenedor de minio (muy top por lo que veo), y le he asignado el subdominio de s3. No solo eso si no que no he tenido que usar docker manualmente para nada y traefik ha actuado el solo para todo lo que he necesitado, ha servido de algo el trabajo de CI/CD :D

Por fin puedo empezar a preparar un servicio!!!! xDDD, ha pasadooooo

1 1 respuesta
bLaKnI

#32 Y por curiosidad, ¿hasta que punto no "has reinventado la rueda" en realidad? Con franqueza...

1 respuesta
Lecherito

#33 Puede ser que la este reinventando muchisimo pero no he visto ningun proyecto que haga lo que yo quiero y como yo lo quiero, asi que estoy haciendo lo que yo necesito. Por ejemplo con el templater existen cosas como gradle init y mvn archetypes pero las dos cosas me parecen basura ademas de que no me parecen demasiado portables, luego go tiene su propio sistema etc etc, no he visto que haya algo mas universal.

Respecto a mis utilidades ni puta idea, ni siquiera he buscado si hay algo parecido o no, es lo que necesito y lo he hecho. No pienso si vale o no, si hay alternativas o no, la mayoria de cosas que estoy haciendo son por aprender ya que me lo paso bastante bien cacharreando.

La ultima vez que hice algo open source porque yo lo necesitaba y no lo deje super abandonado es un proyecto de configuracion que tiene stars en github y gente usandolo en produccion y todo, las necesidades que tenga yo tambien las puede tener mucha otra gente!

1
marveen

En qué HW tiras todo eso?

Me molaría montarles a mis padres un pequeño server con Plex y sonarr/radarr para empezar, nada muy complicado pero imagino que Plex emitiendo en FHD/UHD chupa bastante. Yo en mi piso tengo mi torre y tira sin ningún problema, pero claro, no es lo mismo una CPU de 65w como una de <15W que es mi intención usar en su casa.

2 respuestas
HeXaN

#35 PLEX, si no tienes que hacer transcoding tira en una Game Boy.

Lecherito

Estoy usando un NAS. Un Synology DS718+. https://www.amazon.es/Synology-DS718-Rendimiento-optimizado-intensivas/dp/B075DB49FN

Potencia eléctrica 20.1 vatios

1 respuesta
Lecherito

Al final he hecho un peque;o script para hacer release y subir automaticamente los ficheros a minio asi que tardo poco en hacer release y tenerlo en brew de linux y mac.

Lo siguiente es separarlo de lo hardcoded que tengo en el proyecto para poder usarlo en diferentes sitios y por diferentes personas.

Una vez que haga eso, estuve mirando como manejar las claves ssh que esta empezando a ser un co;azo increible.

Lecherito

Al final he descartado pro ahora hacer lo del hardcode y estuve aprendiendo sobre pgp, gpg, y subclaves ssh. Asi que por ahora lo voy a dejar ahi que ya esta funcionando todo. Una vez que empiece a necesitar mas cosas lo cambiare... pero como se dice, si funciona no lo toques!

Para el templater, le he a;adido que el template pueda tener un fichero .template que puede contener diferente informacion. Como primera toma de contacto, le he puesto comandos que se aplican antes/despues del template.

Un ejemplo:

{
    "commands": {
        "before": [
            "echo 'Before!'"
        ],
        "after": [
            "echo 'After!'"
        ]
    }
}

Por ahora me funciona perfecto, y uno de los usos que necesito de esto son los submodulos de git. Ahora mismo estoy usando un submodulo para las versiones (aunque es algo que me gustaria cambiar a medio-largo plazo) asi que necesito a;adir el submodulo (valdrian publicos y privados, por eso no creo que haya ningun problema)

Ademas se podria tambien hacer que directamente haga un gradle build (O release usando mis utilidades)

marveen

#37 Vale, veo que lleva un Celeron de 10W, si con eso te tira bien creo que tiraré con un Thin Client viejo del curro.

Ahora tendré que mirarme como funciona docker, que siempre he leído sobre el tema pero nunca lo he utilizado.

Lecherito

Mola, ahora puedo ya por fin tener mis submodulos donde los necesite, ademas de hacer un build directamente!

Esto se consigue a;adiendo templating tambien dentro de los comandos, asi que puedes tener el {{name}} y sera sustituido por el nombre (asi como cualquier otra variable)

{
    "commands": {
        "before": [
            "echo 'Before!'"
        ],
        "after": [
            "git -C {{name}} init",
            "git -C {{name}} submodule add -b master myurl",
            "git -C {{name}} submodule init",
            "$SHELL -ic 'cd {{name}} && build'"
        ]
    }
}
1 respuesta
bLaKnI

#41 Por la parte que me toca, debo decir que el thread me interesa, pero lo haces ininteligible. Y creo que a muchos aquí, tenemos tablas como para defendernos en lo que propongas. Pero llevas esto a un nivel de blog personal de progresos, que hace difícil entender siquiera de que va la cosa o el motivo propio del thread.

Puedes ser mas explicito? Enséñanos (si es que quieres hacerlo...) de que es capaz lo que estas montando!

1 1 respuesta
Lecherito

#42 xDDD La verdad es que no es nuevo, se me hace bastante dificil explicarme. En mi cabeza suena espectacular. Esto es un poco nivel de blog personal/diario de cosas que voy haciendo. Como ya dije, ahora mismo estoy sentando los cimientos de lo que me gustaria que fuese mi workflow de development y automatizacion, ya que me gustaria tener todo hecho con Kotlin Nativo (O Rust si me da otro venazo).

Cuando empece esto el templater no estaba todavia listo, ahora creo que esta mejor aunque le queda muchisimo (sobre todo a la espera de Kotlin 1.4). El proyecto del que estoy hablando ahora mismo lo he llamado laguna.

Esta alojado en github, asi que todo el mundo puede echarle un vistazo. Y aqui tengo un asciinema de lo que ahora mismo es capaz de hacer.

Basicamente clona el repositorio de templates y lo usa (mas o menos como haria brew), crea la carpeta con el nombre del proyecto y fichero por fichero los va pasando por el template engine. Luego en el asciinema, se ve que si haces cd y gradle run tienes un hello world con ktor. Lo mismo se podria hacer con spring boot o una aplicacion de Rust, JS o cualquier lenguaje que se preste, es agnostico.

Es mi ultima semana de trabajo, asi que ahora mismo estoy bastante tranquilo por lo que puedo hacer algunas mas cosas como esta, que es sentar los cimientos y expandir mis conocimientos (Tambien estoy investigando sobre OpenTelemetry, PGP como dije... etc). Sinceramente creia que iba a poder automatizar mas cosas, pero me he dado cuenta de que hasta que no tenga mi casa no voy a poder cacharrear tanto como me gustaria y a esto le faltan unos meses :(

1
Lecherito

Banking y transacciones

Pues bueno, ahora que ya tengo todo mas o menos montado me he creado el proyecto que va a albergar el backend para las transacciones bancarias, por ahora llamado Banker.

Bastante sencillo por ahora:

laguna ktor-service --name Banker
cd Banker
git init
git add .
git commit -m "Initial commit"
git remote add <URL>
git push --set-upstream origin master # (Oh god, llamando master a la rama kekw)
<Aqui a;ado el proyecto a mi workspace>
syncws
project Banker # Patrocinado por el autocompletado :D

Con esos simples pasos ya tenia un servicio que podia rular en el puerto 8080 con el framework Ktor de Kotlin (Una especie de Spring).

Luego en 15 minutos mas (entre medias de hacer caca) ya tengo hecho el como responder en Json unas cuantas data clases y ya tengo definidas (temporales) las data clases que voy a usar para las transacciones.

Beans:

data class Transaction(
        val transactionId: UUID,
        val amount: Long,
        val bank: Bank,
        val type: TransactionType
)

enum class TransactionType {
    In, // Receiving money
    Out, // Payment
    Transfer, // Transferring between banks
    ;
}

enum class Bank {
    Caixa,
    BOI,
    Revolut,
    ;
}

Las transacciones con su path:

fun Routing.transactions() {
    getAllTransactions()
}

private fun Routing.getAllTransactions() = get("/transactions") {
    call.respond(listOf(
            Transaction(UUID.randomUUID(), 1L, Bank.Caixa, TransactionType.In)
    ))
}

El main method:

fun main(args: Array<String>) {
    val server = embeddedServer(Netty, port = 8080) {
        install(ContentNegotiation) {
            gson {
                setPrettyPrinting()

                //setDateFormat for the future
            }
        }
        routing {
            ping(this)
            transactions()
        }
    }
    server.start(wait = true)
}

val ping = { routing: Routing ->
    routing.get {
        call.respondText("Pong!", ContentType.Text.Plain)
    }
}

Y con eso ya tenemos el get basico:

➜  ~ curl http://localhost:8080/transactions
[
  {
    "transactionId": "621e0877-1103-4bc7-945c-f88141ca5645",
    "amount": 1,
    "bank": "Caixa",
    "type": "In"
  }
]

Seguramente desactive este path (esto ha sido algo rapidito de prueba), ya que este servicio solo quiero que sea de ingestion de datos y que no se puedan sacar las transacciones. Asi que bueno, por ahora esto va asi y ma;ana seguire otro rato (que ya es mi ultimo dia).

2 2 respuestas
NeV3rKilL

#35 mira que esa cpu de 15w haga transcoding de los codecs que te interesen a las resoluciones que te interesen por hardware y pista. 10% de consumo de cpu. Pero suelen estar limitadas a 1 code/decode simultáneo.

#44 tienes pensado meterle algo de presupuestos? O puro control de gastos?

1 respuesta
bLaKnI

#44 Pero esto se conecta a algún sitio? Donde está el código que lo hace? :\
Y donde imprime o muestra los datos?

2 respuestas
Lecherito

#46 Lo he hecho en 15 minutos, todavia no se conecta a ningun sitio ni nada, esta hardcoded pero estos dias si que voy a ir mas a saco con ello.

#45 No lo se, la verdad, por ahora quiero tener todas mis transacciones metidas y mostrandolas con grafana, como las tengo ahora. Pero ya con un servicio detras.

Lecherito

#46 Bueno, lo prometido es deuda y ya tengo la base de datos!

Base de datos

La bbdd es una postgres normal, nada del otro mundo a la que le he creado una que se llama banker.

Librerias

  1. Ktor se usa para REST.
  2. Exposed con con el driver pgjdbc-ng para interactuar con la base de datos.

Codigo

Para interactuar con la base de datos, he creado una tabla de exposed en la que describo los tipos

object Transactions: Table() {
    val id = long("id").autoIncrement()
    val amount = long("amount")
    val bank = myEnum<Bank>("bank")
    val type = myEnum<TransactionType>("type")

    override val primaryKey = PrimaryKey(id)
    override val tableName = "transactions"
}

Lo de myEnum esta todavia en pruebas ya que es algo relacionado con guardar enums usando su ordinal (aunque quiza lo cambie en el futuro)

Con eso y la database:

val database by lazy {
    Database.connect(
            url = "jdbc:pgsql://localhost:6581/monitoring",
            driver = "com.impossibl.postgres.jdbc.PGDriver",
            user = "monitoring",
            password = "xddddd"
    ).also {
        transaction(it) {
            SchemaUtils.setSchema(Schema("banker"))
            SchemaUtils.createMissingTablesAndColumns(Transactions)
        }
    }
}

Tenemos ya la conextion a la base de datos, con el schema y las tablas/columnas necesarias.

Respecto a las rutas de get y post (que estan en el post de mas arriba), podemos ver como ha cambiado para simplemente insertarlo en la base de datos en vez de hardcoded:

private fun Routing.getAllTransactions() = get("/transactions") {
    val response = bankerTransaction {
        Transactions.selectAll().map { it.asTransaction() }
    }
    call.respond(response)
}

private fun Routing.postTransaction() = post("/transactions") {
    bankerTransaction {
        val transaction = runBlocking { call.receive<Transaction>() }
        Transactions.insert { table ->
            table[amount] = transaction.amount
            table[bank] = transaction.bank
            table[type] = transaction.type
        }
    }
    call.respondOk()
}

Parte cliente

Me hice un playground para poder hacer mis tonterias y descrubri los worksheets de intellij, que son basicamente scripts de kotlin con el contexto del proyecto, asi que me mola porque puedo tener snippets que ejecuto al instante!

Por ejemplo, el cliente seria este:

val transactionsClient = HttpClient(CIO) {
    install(JsonFeature) {
        serializer = GsonSerializer()
    }

    defaultRequest {
        url {
            host = "localhost"
            port = 8080
            path("transactions")
        }
        header(HttpHeaders.ContentType, ContentType.Application.Json)
    }
}

Y luego se usa easy peasy:

runBlocking {
    transactionsClient.post<Unit> {
        body = Transaction(1 /* Aunque esto es inutil, pero bueno */, Random.nextLong(50000), Bank.Caixa, TransactionType.In)
    }
}

Deploy

Ya tengo el Dockerfile preparado, me faltaria hacer que cuando haga push, se haga build del nuevo contenedor y se actualice.

Asi que por ahora bastante chulo (y funcional :D), he de mirar a ver si funciona con una base de datos nueva y poco mas. Luego lo pondre ya en "production", mirare las transacciones que tengo todas y usar el cliente para insertarlas todas.

Wei-Yu

has pensado en categorizarlo? a mí me da algo de pereza pero con matches simples en el concepto/detalles de la transacción debería valer para ir tirando

1 1 respuesta
Lecherito

#49 Si, tendra una descripcion y eso, por ahora solo queria saber como se hacian las cosas etc. La cosa es que cada banco tiene sus propios campos y me gustaria unificarlo de alguna manera. Los campos de la clase Transaction van a cambiar casi seguro, no se todavia si ponerle un UUID o un autoincrement como primary key y estas cosas, son cosas que estoy averiguando conforme estoy investigando.

Estos serian los datos que tengo ahora mismo de cada transaccion en cada banco:

Caixa: Número de cuenta;Oficina;Divisa;F. Operación;F. Valor;Ingreso (+);Gasto (-);Saldo (+);Saldo (-);Concepto común;Concepto propio;Referencia 1;Referencia 2;Concepto complementario 1;Concepto complementario 2
Revolut: Completed Date ; Description ; Paid Out (EUR) ; Paid In (EUR) ; Exchange Out; Exchange In; Balance (EUR); Category; Notes
BOI: Date;Transaction Details;Payments - out;Payments - In

PD: REEEEEEEeeeeeee

claroquesi

Nunca usaria el ordinal de un enum como key de algo. Que locura.
Por que GSON y no Serialization?

Y bueno tampoco veo mucho sentido a ese bloque de post cuando ya tienes un json que le puedes meter de body o crearte un mapping

1 respuesta
Lecherito

#51

  1. Lo del ordinal es algo que ya comento
  2. Tuve un problema con Serialization y no me funcionaba bien, es una de las cosas que tengo pendientes
  3. A que te refieres?
1 respuesta
claroquesi

#52 que no le veo mucho sentido al objeto transaction porque ya conoces los tipos de transactions que puedes hacer. es un enum también.

1 respuesta
Lecherito

#53 ???

1 1 respuesta
claroquesi

#54 que si sigues haciendo enums sin encapsular las cosas vas a tener que hacer unos switch que van a dar miedo. ???

1 respuesta
Lecherito

#55 Los enum son simplemente para no usar Strings. No es cosa de encapsulacion ni voy a tener que usar switch para nada. Aunque sigo sin saber a lo que te refieres exactamente, o muestras un ejemplo en codigo o seguire perdido me parece a mi xD

1 respuesta
claroquesi

#56 entonces nada. si no tienes logica para cada tipo de transaccion no te hara falta.

mola ver que la gente utiliza el ecosistema que ha creado jetbrains. tienen cosas muy curiosas

Lecherito

Pues se me ha ido otra vez el dia con cosas que no tenian nada que ver, solamente van a hacer mas rapido el desarrollo en el futuro xDDDD. Es increible con la rapidez que me invento algo "nuevo".

Cuando estaba usando el Playground y haciendo cosas manual, perdi literalmente 30 minutos porque la clase que estaba usando no era la adecuada (no habia hecho copypasta) y decidi que esas cosas mejor pillarlas a tiempo y arreglarlas de una vez por todas.

Gradle al rescate!

Me he montado un repo de artefactos para guardar mis librerias ahi (por ahora JVM solo) y cree un repositorio nuevo con el modelo del Banker:

create_project BankerModel: Crea el repositorio en el gitlab que esta en el NAS
laguna kotlin-ocean-jvm-lib -n BankerModel: Crea el proyecto con el templater
associate_project: Asocia el proyecto recien creado con el repositorio (remote add, commit, push -u)

Con esto ya tenia el modelo casi listo, lo unico que habia que hacer era copiar y pegar los modelos.

Publishing

Ahora viene lo bueno, para poder usar ese modelo hay que publicarlo en algun lado, y me ha costado lo mio pero al final lo he dejado de una manera casi ideal, lo unico que me falla es que el grupo es el mismo que el nombre asi que la dependencia queda del tipo: BankModel:BankModel:1.0).

Me he creado un plugin para gradle (con los mismos 3 comandos de ahi arriba), en el que configuro el s3 privado y un extension method del proyecto para configurar el plugin con el grupo y la version:

fun Project.configure(libName: String, version: String) {
    apply<OceanLibPlugin>()
    this.group = libName
    this.version = version
}

Y el s3

fun RepositoryHandler.privateS3(bucket: String = "maven", endpoint: String = "http://s3.domain") {
    maven("s3://$bucket") {
        System.setProperty("org.gradle.s3.endpoint", endpoint)
        name = "PrivateS3"
        credentials(AwsCredentials::class.java) {
            accessKey = "access"
            secretKey = "secret"
        }
    }
}

Como el proyecto lo bautice Ocean (las utilities), el metodo para a;adir una dependencia privada desde S3 es un barco!

fun DependencyHandler.ship(lib: String, version: String) {
    add("implementation", "$lib:$lib:$version")
}

Bastante sencillo. Pero no me gustaba como quedaba, mezclaba las librerias privadas y las publicas y eso no me gusta, no se ven las cosas claras y como que las dependencias estan en el sitio que no deberian.

Como ultimo paso, le a;adi el poder para que el plugin lea un fichero Libraries que es basciamente un Json con las dependencias privadas (y estoy pensando en meter ahi tambien las publicas aunque todavia lo estoy pensando). Y no solo eso si no crear un repositorio de aliases donde Gson se transformaria a com.google.code.gson:gson y asi me seria muy sencillo a;adir nuevas librerias, privadas o pulicas con tan solo una palabra.

El formato del fichero Libraries, como dije es un simple Json con por ahora 2 propiedades, implementation y testImplementation que a;adirian las dependencias a su equivalente en gradle

{
    "implementation": {
        "BankerModel": "1.0"
    }
}

Esto en el plugin es bastante trivial una vez que entiendes como funciona lo interno de Gradle:

val libraries = Gson().fromJson(file.readText(), Libraries::class.java)
libraries.implementation.forEach {
    project.dependencies.ship(it.key, it.value)
}
libraries.implementation.forEach {
    project.dependencies.testShip(it.key, it.value)
}

Asi que por ahora bastante contento conforme han quedado las cosas!

No se con que seguire ma;ana, pero bueno, como minimo diria que empezare a ver como hacer deploy del servicio Banker y como hacer build y publish de las librerias automaticamente. Pero eso es algo que esta todavia por ver.

Lecherito

Al final he quitado lo del fichero de librerias, no me gustaba como quedaba (y que no hubiera autocompletar). Y como me encanta que las cosas se vean y se puedan usar de una manera comoda me ha llevado a cambiar de un poco una manera radical conforme lo estaba haciendo

En el plugin he a;adido extension values a la clase que estaba usando para a;adir las dependencias:

val OceanHandler.BankerModel by OceanLibrary

// Proxies
val OceanHandler.KtorServerCore by proxy("io.ktor:ktor-server-core")

Y ahora puedo tener librerias internas (BankerModel), y externas que hacen de proxy (KtorServerCore). Por lo que queda muy chuli

dependencies {
    implementation(kotlin("stdlib"))

    ocean {
        src {
            BankerModel[1.0]
            KtorServerCore["1.3.2"]
            KtorServerNetty["1.3.2"]
            KtorSerialization["1.3.2"]

            LogbackClassic["1.2.3"]

            ExposedCore["0.26.1"]
            ExposedDao["0.26.1"]
            ExposedJdbc["0.26.1"]
            ExposedJavaTime["0.26.1"]
            ExposedMoney["0.26.1"]
            Pgjdbc["0.8.4"]

            Moneta[1.1]
        }
    }
}

Por fin me he quedado contento con como me ha quedado y podre seguir con mi vida. Eso si, me he encontrado con una cosa que no esperaba, no puedo usar repositorios con URL (solo con carpeta /path/to/repo) en laguna, asi que lo siguiente a lo que le dedicare esta noche (o ma;ana), sera a eso.

Ahora mismo el comando: laguna -r git@git.github.com/user/repo template-name -n BestProject no functiona, pero si haces clone manual y usas la carpeta, todo funcionaria a pedir de Milhouse.

eondev

no estás haciendo un poco de overengineering para algo que vas a tener corriendo matao en un NAS? XD
Ya habrías terminado xDD

1 respuesta

Usuarios habituales