WIP: trigger nixops from panel #246

Closed
kiara wants to merge 36 commits from kiara/Fediversity:stitching into main
9 changed files with 119 additions and 7 deletions

View file

@ -58,7 +58,10 @@
packages = [ packages = [
pkgs.nil pkgs.nil
inputs'.agenix.packages.default inputs'.agenix.packages.default
inputs'.nixops4.packages.default (inputs'.nixops4.packages.default.overrideAttrs {
impureEnvVars = [ "DEPLOYMENT" ];
})
pkgs.openssh
pkgs.httpie pkgs.httpie
pkgs.jq pkgs.jq
]; ];

View file

@ -143,7 +143,17 @@ in
## - We add a “test” deployment with all test machines. ## - We add a “test” deployment with all test machines.
nixops4Deployments = genAttrs machines makeDeployment' // { nixops4Deployments = genAttrs machines makeDeployment' // {
default = makeDeployment machines; default = makeDeployment machines;
test = makeTestDeployment (fromJSON (readFile ./test-machines/configuration.json)); test = makeTestDeployment (
fromJSON (
let
env = builtins.getEnv "DEPLOYMENT";
in
if env != "" then
env
else
builtins.trace "env var DEPLOYMENT not set, falling back to ./test-machines/configuration.json!" (readFile ./test-machines/configuration.json)
)
);
}; };
flake.nixosConfigurations = flake.nixosConfigurations =
genAttrs machines (makeConfiguration false) genAttrs machines (makeConfiguration false)

View file

@ -1,17 +1,26 @@
{ {
# inputs,
config, config,
pkgs,
... ...
}: }:
let let
name = "panel"; name = "panel";
panel = (import ../../../panel/default.nix { }).package; panel = (import ../../../panel/default.nix { }).package;
in in
# builtins.trace args.pkgs
{ {
imports = [ imports = [
../../../panel/nix/configuration.nix ../../../panel/nix/configuration.nix
]; ];
nix.settings = {
extra-experimental-features = "configurable-impure-env";
};
environment.systemPackages = [ environment.systemPackages = [
# inputs.self.outPath
# ../../..
panel panel
]; ];
@ -33,7 +42,12 @@ in
settings = { settings = {
DATABASE_URL = "sqlite:///var/lib/${name}/db.sqlite3"; DATABASE_URL = "sqlite:///var/lib/${name}/db.sqlite3";
CREDENTIALS_DIRECTORY = "/var/lib/${name}/.credentials"; CREDENTIALS_DIRECTORY = "/var/lib/${name}/.credentials";
STATIC_ROOT = "/var/lib/${name}/static";
}; };
}; };
systemd.services.${name}.environment = {
STATIC_ROOT = "/var/lib/${name}/static";
# REPO_DIR = inputs.self.outPath;
REPO_DIR = ../../..;
NIX_DIR = pkgs.nix;
};
} }

View file

@ -34,6 +34,7 @@ in
export CREDENTIALS_DIRECTORY=${builtins.toString ./.credentials} export CREDENTIALS_DIRECTORY=${builtins.toString ./.credentials}
export DATABASE_URL="sqlite:///${toString ./src}/db.sqlite3" export DATABASE_URL="sqlite:///${toString ./src}/db.sqlite3"
''; '';
NIX_DIR = pkgs.nix;
}; };
tests = pkgs'.callPackage ./nix/tests.nix { }; tests = pkgs'.callPackage ./nix/tests.nix { };

View file

@ -39,11 +39,11 @@ class Configuration(BaseModel):
# XXX: hard-code available apex domains for now, # XXX: hard-code available apex domains for now,
# they will be prefixed by the user name # they will be prefixed by the user name
class Domain(Enum): class Domain(Enum):
EU = "fediversity.eu" # EU = "fediversity.eu"
NET = "fediversity.net" NET = "fediversity.net"
domain: Domain = Field( domain: Domain = Field(
default=Domain.EU, default=Domain.NET,
description="DNS domain where to expose services" description="DNS domain where to expose services"
) )

View file

@ -3,3 +3,30 @@ body
margin: 0 margin: 0
font-family: sans-serif font-family: sans-serif
box-sizing: border-box box-sizing: border-box
.loader
width: 48px
height: 48px
border: 5px solid #000
border-bottom-color: #F34508
border-radius: 50%
display: inline-block
box-sizing: border-box
animation: rotation 1s linear infinite
@keyframes rotation
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
#spinner-container
position: absolute
top: 50%
left: 50%
transform: translate(-50%, -50%)
display: block
.htmx-indicator
display: none
.htmx-request .htmx-indicator
display: inline-block

View file

@ -5,6 +5,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
{% load compress %} {% load compress %}
{% compress css %} {% compress css %}

View file

@ -5,7 +5,18 @@
{{ form.as_p }} {{ form.as_p }}
<button class="button" disabled>Deploy</button> <button id="deploy-button" class="button"
<button class="button" type="submit" >Save</button> hx-post="{% url 'configuration_form' %}"
hx-trigger="click"
hx-indicator="#spinner-container">
Deploy
<span class="htmx-indicator loader"></span>
</button>
<button class="button" type="submit" name="save">Save</button>
<div id="spinner-container" class="htmx-indicator">
<span class="loader"></span>
</div>
</form> </form>
{% endblock %} {% endblock %}

View file

@ -1,15 +1,22 @@
from enum import Enum from enum import Enum
import os
import json
import threading
from django.urls import reverse_lazy from django.urls import reverse_lazy
import os
import subprocess
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.views.generic import TemplateView, DetailView from django.views.generic import TemplateView, DetailView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.http import JsonResponse
from panel import models from panel import models
from panel.configuration import forms from panel.configuration import forms
class Index(TemplateView): class Index(TemplateView):
template_name = 'index.html' template_name = 'index.html'
@ -41,6 +48,44 @@ class ConfigurationForm(LoginRequiredMixin, FormView):
operator=self.request.user, operator=self.request.user,
) )
if "deploy" in self.request.POST.keys():
threading.Thread(target=self.run_deployment, args=(obj,)).start()
Review

@kevin do we have a way to return info back to the client this way? or would the client know what it needs to know for now?

@kevin do we have a way to return info back to the client this way? or would the client know what it needs to know for now?
return obj
def run_deployment(self, obj):
if "deploy" in self.request.POST.keys():
Review

@kevin is there a reason we perform this check twice now?

@kevin is there a reason we perform this check twice now?
print("DEPLOYING:")
print(os.getenv("REPO_DIR"))
print(os.getenv("NIX_DIR"))
submission = obj.parsed_value.model_dump_json()
deployment = json.dumps(json.loads(submission) | {
"initialUser": {
"displayName": "Testy McTestface",
"username": "test",
"password": "testtest",
"email": "test@test.com",
},
})
env = {
"DEPLOYMENT": deployment,
"PATH": f"{os.getenv("NIX_DIR")}/bin/",
}
print(f"env: {env}")
print(f"Path: {os.getcwd()}/..")
cmd = [
"nix",
"develop",
"--extra-experimental-features",
"configurable-impure-env",
"--command",
"nixops4",
"--show-trace",
"--verbose",
"apply",
"test",
]
subprocess.run(cmd, cwd=os.getenv("REPO_DIR") or f"{os.getcwd()}/..", env=env)
return obj return obj
# TODO(@fricklerhandwerk): # TODO(@fricklerhandwerk):