Initial version

main
Celso González 2023-03-06 00:54:12 +01:00
commit 1b52bb2620
17 changed files with 563 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
ansible/inventory.yml
debian10-ssh.img
vm.xml

View File

@ -0,0 +1,12 @@
# syntax=docker/dockerfile:1
FROM golang:latest as gobuild
RUN mkdir /app
ADD . /app
WORKDIR /app
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags '-w -extldflags "-static"' -o go-backend
FROM scratch
COPY --from=gobuild /app/go-backend /bin/go-backend
ENTRYPOINT ["/bin/go-backend"]

Binary file not shown.

View File

@ -0,0 +1,3 @@
module immfly/go-backend
go 1.20

View File

@ -0,0 +1,32 @@
package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func clock(w http.ResponseWriter, r *http.Request) {
currentTime := float64(time.Now().UnixMicro())/float64(1e6)
io.WriteString(w, fmt.Sprintf("%07f", currentTime))
}
func main() {
port, ok := os.LookupEnv("BACKEND_PORT")
if !ok {
//Default value 5050
port = "5050"
}
mux := http.NewServeMux()
mux.HandleFunc("/clock", clock)
fmt.Printf("Starting backend server in port %s\n", port)
err := http.ListenAndServe(fmt.Sprintf(":%s", port), mux)
if err != nil {
fmt.Printf("Failed to start server: %v\n", err)
}
}

View File

@ -0,0 +1,24 @@
---
- name: Copy backend files
ansible.builtin.copy:
src: go-backend
dest: /home/nonroot/
owner: nonroot
group: nonroot
- name: Build image with go-backend
community.docker.docker_image:
build:
path: /home/nonroot/go-backend
name: go-backend
source: build
- name: Start the docker image to serve the backend
community.docker.docker_container:
name: backend
image: go-backend
exposed_ports:
- 5050
become_user: nonroot

View File

@ -0,0 +1,59 @@
---
# https://docs.docker.com/engine/install/debian/
- name: Ensure required packages are installed
apt:
name: [
'ca-certificates',
'curl',
'gnupg',
'lsb-release',
]
state: latest
- name: We add the docker key to the keyring
block:
- name: Ensure directory exists
file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
- name: We download the key
ansible.builtin.apt_key:
url: https://download.docker.com/linux/debian/gpg
keyring: /etc/apt/keyrings/docker.gpg
- name: We add the docker repository
ansible.builtin.apt_repository:
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
state: present
filename: docker
- name: Docker installation
apt:
name: [
'docker-ce',
'docker-ce-cli',
'containerd.io',
'docker-buildx-plugin',
'docker-compose-plugin',
]
state: latest
- name: Create a nonroot user to run docker images
ansible.builtin.user:
name: nonroot
shell: /bin/bash
groups: docker
- name: Install python dependencies to use docker community module
block:
- name: Install python pip
apt:
name: python3-pip
state: latest
- name: Install docker pip sdk
ansible.builtin.pip:
name: docker

View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html>
<head>
<title>Clocker</title>
<style type="text/css">
html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
body {
background: #030303;
color: #42eef4;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.clock {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-weight: bold;
font-family: monospace;
text-shadow: 0 0 10px #42eef4;
}
#clockTime {
font-size: 8em;
}
#clockDate {
font-size: 4em;
}
</style>
</head>
<body>
<div class="clock">
<div id="clockTime"></div>
<div id="clockDate"></div>
</div>
<script>
((window, document, undefined) =>
{
'use strict';
const CLOCK_URL = '/clock';
const clockTime = document.getElementById('clockTime');
const clockDate = document.getElementById('clockDate');
function fetchEpoch(onFetch)
{
return fetch(CLOCK_URL)
.then(response =>
response.text().then(onFetch)
);
}
function refreshClock()
{
return fetchEpoch(setClockEpoch);
}
function setClockEpoch(epoch)
{
const date = new Date(0);
date.setUTCSeconds(epoch);
clockTime.textContent = formatTime(date);
clockDate.textContent = formatDate(date);
}
function formatDate(date)
{
return [
('0' + date.getDate()).slice(-2),
('0' + (date.getMonth() + 1)).slice(-2),
date.getFullYear(),
].join('/');
}
function formatTime(date)
{
return [
("0" + date.getHours()).slice(-2),
("0" + date.getMinutes()).slice(-2),
("0" + date.getSeconds()).slice(-2),
].join(':');
}
function clockInterval(interval)
{
return setInterval(refreshClock, interval || 500)
}
clockInterval();
})(window, document, undefined);
</script>
</body>
</html>

View File

@ -0,0 +1,27 @@
server {
listen 80 default_server;
listen [::]:80;
server_name _;
access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /clock {
proxy_pass http://backend:5050/clock;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -0,0 +1,37 @@
---
- name: Ensure frontend dir exists
file:
path: "{{ item }}"
owner: nonroot
group: nonroot
state: directory
mode: '0755'
with_items:
- "/home/nonroot/frontend/html"
- "/home/nonroot/frontend/config"
- name: Copy the frontend files
copy:
src: "{{ item.file }}"
dest: "/home/nonroot/frontend/{{ item.dest }}"
owner: nonroot
group: nonroot
with_items:
- { file: 'index.html', dest: "html" }
- { file: 'nginx.conf', dest: "config" }
- name: Start an nginx image to serve the files
community.docker.docker_container:
name: frontend
image: nginx
links:
- backend
ports:
- "80:80"
volumes:
- /home/nonroot/frontend/html:/usr/share/nginx/html
- /home/nonroot/frontend/config/nginx.conf:/etc/nginx/conf.d/default.conf
become_user: nonroot

View File

@ -0,0 +1,14 @@
---
# This playbook will install
# docker requirements to run the docker machines
# backend developed in go and running in a docker machine
# frontend serving static page using nginx
- name: tech-test-infra
hosts: immfly_debian10
roles:
- docker
- backend
- frontend

110
assets/index.html Normal file
View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html>
<head>
<title>Clocker</title>
<style type="text/css">
html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
body {
background: #030303;
color: #42eef4;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.clock {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-weight: bold;
font-family: monospace;
text-shadow: 0 0 10px #42eef4;
}
#clockTime {
font-size: 8em;
}
#clockDate {
font-size: 4em;
}
</style>
</head>
<body>
<div class="clock">
<div id="clockTime"></div>
<div id="clockDate"></div>
</div>
<script>
((window, document, undefined) =>
{
'use strict';
const CLOCK_URL = '/clock';
const clockTime = document.getElementById('clockTime');
const clockDate = document.getElementById('clockDate');
function fetchEpoch(onFetch)
{
return fetch(CLOCK_URL)
.then(response =>
response.text().then(onFetch)
);
}
function refreshClock()
{
return fetchEpoch(setClockEpoch);
}
function setClockEpoch(epoch)
{
const date = new Date(0);
date.setUTCSeconds(epoch);
clockTime.textContent = formatTime(date);
clockDate.textContent = formatDate(date);
}
function formatDate(date)
{
return [
('0' + date.getDate()).slice(-2),
('0' + (date.getMonth() + 1)).slice(-2),
date.getFullYear(),
].join('/');
}
function formatTime(date)
{
return [
("0" + date.getHours()).slice(-2),
("0" + date.getMinutes()).slice(-2),
("0" + date.getSeconds()).slice(-2),
].join(':');
}
function clockInterval(interval)
{
return setInterval(refreshClock, interval || 500)
}
clockInterval();
})(window, document, undefined);
</script>
</body>
</html>

7
assets/inventory.yml Normal file
View File

@ -0,0 +1,7 @@
---
all:
hosts:
immfly_debian10:
ansible_host: VM_IP
ansible_ssh_private_key_file: ../assets/rsa

49
assets/rsa Normal file
View File

@ -0,0 +1,49 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

1
assets/rsa.pub Normal file
View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDAuOMkJ8KVP/ws/wFdQSbuJKGYAhYxX+XsrFrIw6coSvjv0YkaQ3aqn2lDrWvSpwonfubSjX2/QEc1ac4yWElq4cxJqhCj6QckbhsqaLi1vUtuWilDpGO0q2iguaYbFH6/9HxrJr7HCJ+zYXX+0bMOXPnz9dyGcVnGo9MiRvFwKWrvEhXjTSApzjUxkeqEcO+qChY+GScufr6oLeZNUqk8MQLGWzMo5YiW1/1Nqj7mhqC5ovrkeIEBpkDdKQuEp0NuOWo6iRRgRLlAVZV4uYXzH4V+KpcaH7KQWj2QJefGCbfmzo8y8JpaOBSfy5iagSHsOGIKDRJcbaZzhjY/HFDhv1M4VojtvVFWBNk6sI8khU5Wwj414atH3qgRp4mgmMaa2+v/NANNuBph5gNHjJyRhfsjKol4UD8PGpkeWou3RPfGBl/Q78pTyTdxGcmF1U+1IqZY8kPCVu00QSGQzIauVNkrHI5Wrn9G3wsAzP6MOLhsUSnDhN2fVMnqXGT9Tm1tYWI8csNXpZoGdXVQcPsUdEkdoLeDSWeYvXo7L73tJ7oAsgviHwRJzizG7qRCIIyVTQ+kzllNTwvRNSGlpu/17SDTPXxDPDIMqwyduM9L9iuZaG8l/Y39Vfc9LNdJW66MegJl+xdwUnJ4ybP3M/wtXV/9J6+5yBUe/0ZhcoM9Mw==

43
assets/vm.xml Normal file
View File

@ -0,0 +1,43 @@
<domain type='qemu' id='9'>
<name>immfly-debian10</name>
<uuid>4b68e38d-1bbc-4327-9da1-be6f688b182a</uuid>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-q35-3.1'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<vmport state='off'/>
</features>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='${PATH_TO_VM_DISK_FILE}'/>
<backingStore/>
<target dev='vda' bus='virtio'/>
<alias name='virtio-disk0'/>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</disk>
<interface type='network'>
<mac address='52:54:00:c0:e9:b1'/>
<source network='default' bridge='virbr0'/>
<target dev='vnet0'/>
<model type='virtio'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</interface>
<video>
<model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>
<alias name='video0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0'/>
</video>
</devices>
</domain>

32
run_all_processes.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
echo "Downloading and decompressing vm imaage"
if [ -f debian10-ssh.img ]; then
echo " -> Alreday done using cached version"
else
wget https://immfly-infra-technical-test.s3-eu-west-1.amazonaws.com/debian10-ssh.img.tar.xz
tar xf debian10-ssh.img.tar.xz
rm -f debian10-ssh.img.tar.xz
fi
#Fix the route in the vm file
sed "s+\${PATH_TO_VM_DISK_FILE}+$PWD/debian10-ssh.img+" assets/vm.xml > vm.xml
echo "Start the VM"
virsh create vm.xml
#We get the ip
IP=`virsh domifaddr immfly-debian10 | grep ipv4 | awk '{ print $4 }' | sed 's/\/.*//'`
echo "VM ip obtained: $IP"
#Pequeño hack para asegurar que la conexion ssh se ha levantado
until ssh-keyscan $IP ; do sleep 3 ; done
#Upgrade the ansible inventory
sed "s/VM_IP/$IP/" assets/inventory.yml > ansible/inventory.yml
#Run the playbook
cd ansible
ansible-playbook -i inventory.yml -v tech-test-infra.yml