Compare commits

..

50 Commits

Author SHA1 Message Date
4c775c21e4 add v1.applicationservice.dependency.add message 2020-02-23 16:53:29 +01:00
c7716da327 add applicationservice.dependency.add message 2020-02-21 16:16:50 +01:00
555ea2beb7 remove source.release.get_by_distribution message 2020-02-21 16:10:06 +01:00
7757912b55 do not ask release_id 2020-02-21 16:08:31 +01:00
251b521274 log on_join function 2020-01-31 11:34:27 +01:00
ab71686633 add v1.servermodel.create message 2020-01-31 11:23:41 +01:00
3b3cefa38a add v1.servermodel.create message 2020-01-31 11:23:18 +01:00
6bdf21d1ac tiramisu in postgres database 2020-01-30 16:22:06 +01:00
5666c01bdc update docker 2020-01-23 11:01:38 +01:00
3a9243bfb8 add docker environment 2020-01-20 15:58:04 +01:00
bcd17e1038 set docker 2020-01-20 11:34:16 +01:00
47d5ed77d5 set requirements 2020-01-17 15:59:48 +01:00
02c38589d4 add role server_rw + role example + deploy 2020-01-15 09:15:15 +01:00
cb2dbe135e add message 2020-01-14 20:05:28 +01:00
cb0e4b5d5d create risotto admin and set rights 2020-01-14 14:11:41 +01:00
722d4894a1 simplify 2020-01-13 19:53:09 +01:00
4020f97db0 insert log in database 2019-12-28 12:29:11 +01:00
a6383f0c2c check_role for event type message 2019-12-27 16:17:34 +01:00
1e223e7b57 add default uri roles 2019-12-27 16:07:29 +01:00
24e5f78668 new users are in 'all' role 2019-12-27 16:06:57 +01:00
5b1cae1567 create a permanent pool instead of 1 per use 2019-12-27 16:03:42 +01:00
1ed86e035b remove public information in message, it's remplace by role 2019-12-27 15:25:44 +01:00
94168554f2 role support 2019-12-27 15:09:38 +01:00
50aa8019ab update tests 2019-12-26 15:33:51 +01:00
1d25d3a582 tiramisu is now async 2019-12-26 11:38:31 +01:00
41af2512b5 extra support 2019-12-22 18:41:35 +01:00
4de9bde691 server>template 2019-12-20 10:58:12 +01:00
6b8a88e103 Temporary fixes for internal default value 2019-12-20 10:37:27 +01:00
9335fbb16e message servermodel.get_by_id 2019-12-20 10:19:49 +01:00
78b7129605 Fixes local paths 2019-12-20 10:17:37 +01:00
b5ddefdaac Compute messages root path against project root dir in development environment 2019-12-20 10:16:12 +01:00
ddd97fb59c can create a server 2019-12-19 17:24:20 +01:00
77ed63784b really create schema 2019-12-19 15:00:24 +01:00
f7a97cf575 source>release>applicationservice>servermodel 2019-12-19 12:25:16 +01:00
a092b597f8 Modify distribution 2019-12-19 09:19:09 +01:00
e19e718e22 import servermodel 2019-12-18 17:11:42 +01:00
b006eda133 Do not raise error if folder exists 2019-12-18 10:08:11 +01:00
62ab525219 Open file in write mode 2019-12-17 17:07:58 +01:00
10969ab1e0 reworks on tests 2019-12-16 17:14:58 +01:00
f0042f2a37 Fetch releases.yml to assert if Source is valid 2019-12-16 16:35:48 +01:00
7f0411da4d local_<servermodel_name> application has to be created in internal source 2019-12-16 16:17:37 +01:00
91aac5399a Add Distribution attributute to releases 2019-12-16 15:49:20 +01:00
b4c48ebc10 add source.list, source.create and source.get messages 2019-12-16 15:13:28 +01:00
b567fd88ac WIP load servermodel with parent 2019-12-13 17:17:07 +01:00
eccc5c4098 WIP2 2019-12-13 16:42:10 +01:00
a7934e37d7 WIP 2019-12-13 13:55:30 +01:00
7dc6ce7845 L’extension doit être créée pour la base de données risotto 2019-12-09 16:28:47 +01:00
84850182f6 Activation de l’extension hstore 2019-12-09 16:22:13 +01:00
dcaf7da3bc Test d’un service utilisant une base de données. 2019-12-09 14:28:13 +01:00
8c91e01a2b Vocabulary 2019-12-09 14:28:13 +01:00
112 changed files with 3902 additions and 1526 deletions

113
README.md
View File

@ -11,20 +11,115 @@ Accéder à un message :
wget http://localhost:8080/v1/config.session.server.start
```
[Documentation WIKI cadoles](https://doku.cadoles.com/dokuwiki/doku.php?id=documentation:risotto) n'est qu'un liens vers la doc officiel (pour le moment) [dans la forge](https://forge.cadoles.com/Infra/eole-risotto/wiki)
Démarrer un serveur LemonLDAP de test
```
docker pull coudot/lemonldap-ng
echo "127.0.0.1 auth.example.com manager.example.com test1.example.com test2.example.com" >> /etc/hosts
docker run -d --add-host reload.example.com:127.0.0.1 -p 80:80 coudot/lemonldap-ng
```
**branche :**
Démarrer un serveur postgresql de test
```
docker run -dt -p 5432:5432 --name postgres postgres:11-alpine
docker exec -ti postgres bash
master
psql -U postgres -h localhost -c "CREATE ROLE risotto WITH LOGIN PASSWORD 'risotto';"
psql -U postgres -h localhost -c "CREATE DATABASE risotto;"
psql -U postgres -h localhost -c "GRANT ALL ON DATABASE risotto TO risotto;"
#psql -U postgres -h localhost -c "CREATE EXTENSION hstore;" risotto
```
develop
Gestion de la base de données avec Sqitch
dist/risotto/risotto-2.7.1/develop : pour faire des paquets
```
cpanm --quiet --notest App::Sqitch
sqitch init risotto --uri https://forge.cadoles.com/Infra/risotto --engine pg
```
dist/risotto/risotto-2.8.0/develop : pour faire des paquets
docker : n'est pas a jour
Commande :
feature/service_servermodel :
# Empty database:
su - postgres
psql -U postgres risotto
drop table log; drop table userrole; drop table release; drop table source; drop table server; drop table servermodel; drop table applicationservice; drop table roleuri; drop table risottouser; drop table uri;
jwt :
psql -U postgres tiramisu
drop table value; drop table property; drop table permissive; drop table information; drop table session;
# Import EOLE
./script/cucchiaiata source.create -n eole -u http://localhost
./script/cucchiaiata source.release.create -s eole -n 2.7.1.1 -d last
./script/cucchiaiata applicationservice.dataset.updated -s eole -r last
./script/cucchiaiata servermodel.dataset.updated -s eole -r last
# Create a new user and set role 'server_rw' for this server
./script/cucchiaiata user.create -l gnunux -n gnunux -s gnunux
./script/cucchiaiata user.role.create -u gnunux -n 'server_rw' -a 'Server.ServerName' -v test
# Heritage
## ACA
./script/cucchiaiata servermodel.create -n aca -d Aca -p eolebase -s eole -r last
./script/cucchiaiata session.servermodel.start -s aca
S=xxxxxxxxxxxxxxxxxxxxxx
### verif
./script/cucchiaiata session.servermodel.get -s $S -n creole.reseau.unbound_route_address
./script/cucchiaiata session.servermodel.get -s $S -n creole.reseau.unbound_domain_name
./script/cucchiaiata session.servermodel.get -s $S -n creole.serveur_dns.unbound_local_zones
./script/cucchiaiata session.servermodel.get -s $S -n creole.reseau.unbound_ip_address_cidr
./script/cucchiaiata session.servermodel.configure -s $S --creole.reseau.unbound_route_address 192.168.1.2
./script/cucchiaiata session.servermodel.configure -s $S --creole.serveur_dns.unbound_allowed_client_cidr 192.168.1.0/24
./script/cucchiaiata session.servermodel.stop -s $S -a
## etab1
./script/cucchiaiata servermodel.create -n etab1 -d "Etab 1" -p aca -s internal -r last
./script/cucchiaiata session.servermodel.start -s etab1
S=xxxxxxxxxxxxxxxxxxxxxx
./script/cucchiaiata session.servermodel.configure -s $S --creole.reseau.unbound_domain_name test.cadoles.com
./script/cucchiaiata session.servermodel.stop -s $S -a
## unbound
./script/cucchiaiata servermodel.create -n unbound -d "generic unbound configuration" -p eolebase -s eole -r last
./script/cucchiaiata session.servermodel.start -s unbound
S=xxxxxxxxxxxxxxxxxxxxxx
./script/cucchiaiata session.servermodel.configure -s $S --creole.serveur_dns.unbound_local_zones cadoles.com
./script/cucchiaiata session.servermodel.filter -s $S -n unbound
./script/cucchiaiata session.servermodel.configure -s $S --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.hostname_cadoles_com toto titi
./script/cucchiaiata session.servermodel.configure -s $S --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.ip_cadoles_com 0 192.168.1.25
./script/cucchiaiata session.servermodel.configure -s $S --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.type_cadoles_com 1 CNAME
./script/cucchiaiata session.servermodel.configure -s $S --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.cname_cadoles_com 1 toto.cadoles.com
./script/cucchiaiata session.servermodel.stop -s $S -a
## unbound_etab1
./script/cucchiaiata servermodel.create -n unbound_etab1 -d "unbound configuration for etab1" -p etab1 unbound -s internal -r last
### verif
./script/cucchiaiata session.servermodel.start -s unbound_etab1
S=xxxxxxxxxxxxxxxxxxxxxx
XXXXX
# Create a server
./script/cucchiaiata server.create -s test -d description -m unbound_etab1 -r last
./script/cucchiaiata session.server.start -s test
S=xxxxxxxxxxxxxxxxxxxxxx
./script/cucchiaiata session.server.configure -s $S --creole.reseau.unbound_ip_address_cidr 192.168.1.1/24
./script/cucchiaiata session.server.validate -s $S
./script/cucchiaiata session.server.stop -s $S -a
# Generate configuration
./script/cucchiaiata config.configuration.server.deploy -s test
./script/cucchiaiata template.generate -s test
# OpenSSH
#add servermodel openssh link to eolebase
#add servermodel openssh2 lint to openssh
#link openssh applicationservice to this servermodel
./script/cucchiaiata servermodel.create -n openssh_1 -d description -p eolebase -s eole -r last
./script/cucchiaiata servermodel.create -n openssh_2 -d openssh_2 -p openssh_1 -s internal -r last
./script/cucchiaiata applicationservice.dependency.add -n local_openssh_1 -a openssh -s eole -r last
#./script/cucchiaiata server.create -s test -d description -m eolebase -r last

29
docker/Dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM python:3.7
# Requirements
ARG TIRAMISU_REPO_URL=https://framagit.org/tiramisu/tiramisu.git
ARG RISOTTO_REPO_URL=https://forge.cadoles.com/Infra/risotto.git
ARG ROUGAIL_REPO_URL=https://forge.cadoles.com/Infra/rougail.git
RUN apt-get update && apt-get install -y \
vim \
curl \
git \
jq \
&& apt-get clean
RUN git clone --branch develop ${TIRAMISU_REPO_URL} /srv/src/tiramisu
RUN git clone --branch docker ${RISOTTO_REPO_URL} /srv/src/risotto
RUN git clone --branch master ${ROUGAIL_REPO_URL} /srv/src/rougail
RUN ln -s /srv/src/tiramisu/tiramisu /usr/local/lib/python3.7
RUN ln -s /srv/src/rougail/src/rougail /usr/local/lib/python3.7
RUN ln -s /srv/src/risotto/src/risotto /usr/local/lib/python3.7
RUN pip install Cheetah3
RUN cd /srv/src/risotto && pip install -r requirements.txt
# Installation
RUN cp -r /srv/src/risotto/messages/ /usr/local/lib/
RUN mkdir -p /var/cache/risotto/servermodel
RUN mkdir -p /var/cache/risotto/database

14
docker/README.md Normal file
View File

@ -0,0 +1,14 @@
Docker
```
cd docker
docker build -t cadoles/risotto .
docker run -t -d --name risotto cadoles/risotto
docker exec -ti risotto bash
```
Docker-Compose
```
cd docker
docker-compose up
```

View File

@ -0,0 +1,31 @@
version: '2.2'
services:
risotto:
build:
context: ../
dockerfile: docker/Dockerfile
volumes:
- ../.:/srv/src/risotto
ports:
- "8080:8080"
depends_on:
- postgres
links:
- postgres
#command: tail -F /var/log
command: python -u /srv/src/risotto/script/server.py
restart: on-failure
environment:
RISOTTO_DSN: ${RISOTTO_DSN:-postgres://risotto:risotto@postgres:5432/risotto}
RISOTTO_TIRAMISU_DSN: ${RISOTTO_TIRAMISU_DSN:-postgres://risotto:risotto@postgres:5432/tiramisu}
postgres:
image: postgres:11-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
PGDATA: /data/postgres
volumes:
- ./postgres-init/:/docker-entrypoint-initdb.d/
ports:
- "5432:5432"
restart: unless-stopped

View File

@ -0,0 +1,103 @@
#!/bin/bash
set -e
psql --username "$POSTGRES_USER" <<-EOSQL
CREATE ROLE risotto WITH LOGIN PASSWORD 'risotto';
CREATE DATABASE risotto;
GRANT ALL ON DATABASE risotto TO risotto;
\c risotto
CREATE EXTENSION hstore;
EOSQL
psql --username "risotto" --password "risotto" <<-EOSQL
-- Création de la table Source
CREATE TABLE Source (
SourceId SERIAL PRIMARY KEY,
SourceName VARCHAR(255) NOT NULL UNIQUE,
SourceURL TEXT
);
-- Création de la table Release
CREATE TABLE Release (
ReleaseId SERIAL PRIMARY KEY,
ReleaseName VARCHAR(255) NOT NULL,
ReleaseSourceId INTEGER NOT NULL,
ReleaseDistribution VARCHAR(20) CONSTRAINT releasedistribution_choice CHECK (ReleaseDistribution IN ('last', 'n-1', 'n-2')),
UNIQUE (ReleaseName, ReleaseSourceId),
UNIQUE (ReleaseDistribution, ReleaseSourceId),
FOREIGN KEY (ReleaseSourceId) REFERENCES Source(SourceId)
);
-- Création de la table Servermodel
CREATE TABLE Servermodel (
ServermodelId SERIAL PRIMARY KEY,
ServermodelName VARCHAR(255) NOT NULL,
ServermodelDescription VARCHAR(255) NOT NULL,
ServermodelParentsId INTEGER [] DEFAULT '{}',
ServermodelReleaseId INTEGER NOT NULL,
ServermodelApplicationServiceId INTEGER NOT NULL,
ServermodelUsers hstore,
UNIQUE (ServermodelName, ServermodelReleaseId)
);
-- Création de la table ApplicationService
CREATE TABLE ApplicationService (
ApplicationServiceId SERIAL PRIMARY KEY,
ApplicationServiceName VARCHAR(255) NOT NULL,
ApplicationServiceDescription VARCHAR(255) NOT NULL,
ApplicationServiceReleaseId INTEGER NOT NULL,
ApplicationServiceDependencies JSON,
UNIQUE (ApplicationServiceName, ApplicationServiceReleaseId)
);
-- Server table creation
CREATE TABLE Server (
ServerId SERIAL PRIMARY KEY,
ServerName VARCHAR(255) NOT NULL UNIQUE,
ServerDescription VARCHAR(255) NOT NULL,
ServerServermodelId INTEGER NOT NULL
);
-- User, Role and ACL table creation
CREATE TABLE RisottoUser (
UserId SERIAL PRIMARY KEY,
UserLogin VARCHAR(100) NOT NULL UNIQUE,
UserName VARCHAR(100) NOT NULL,
UserSurname VARCHAR(100) NOT NULL
);
CREATE TABLE UserRole (
RoleId SERIAL PRIMARY KEY,
RoleUserId INTEGER NOT NULL,
RoleName VARCHAR(255) NOT NULL,
RoleAttribute VARCHAR(255),
RoleAttributeValue VARCHAR(255),
FOREIGN KEY (RoleUserId) REFERENCES RisottoUser(UserId)
);
CREATE TABLE URI (
URIId SERIAL PRIMARY KEY,
URIName VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE RoleURI (
RoleName VARCHAR(255) NOT NULL,
URIId INTEGER NOT NULL,
FOREIGN KEY (URIId) REFERENCES URI(URIId),
PRIMARY KEY (RoleName, URIId)
);
-- Log table creation
CREATE TABLE log(
Msg VARCHAR(255) NOT NULL,
Level VARCHAR(10) NOT NULL,
Path VARCHAR(255),
Username VARCHAR(100) NOT NULL,
Data JSON,
Date timestamp DEFAULT current_timestamp
);
EOSQL

View File

@ -0,0 +1,28 @@
---
uri: applicationservice.create
description: |
Créé un service applicatif.
pattern: rpc
parameters:
applicationservice_name:
type: String
shortarg: n
description: |
Nom du service applicatif à créer.
applicationservice_description:
type: String
shortarg: d
description: |
Description du service applicatif à créer.
applicationservice_dependencies:
type: '[]Number'
shortarg: a
description: ID des services applicatif donc dépendant le service applicatif.
default: []
response:
type: ApplicationService
description: Informations sur le service applicatif créé.

View File

@ -0,0 +1,20 @@
uri: applicationservice.dataset.updated
description: |
Les services applicatifs sont mis à jour.
pattern: rpc
parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
release_distribution:
type: String
shortarg: r
description: Distribution de la source.
response:
type: ReturnStatus
description: Code de retour sur linjection des services applicatifs en base.

View File

@ -0,0 +1,30 @@
---
uri: applicationservice.dependency.add
description: |
Ajouter une dépendance à un service applicatif.
pattern: rpc
parameters:
applicationservice_name:
type: String
shortarg: n
description: |
Nom du service applicatif.
applicationservice_dependency:
type: String
shortarg: a
description: Service applicatif donc dépendant le service applicatif.
source_name:
type: String
shortarg: s
description: Source du service applicatif a ajouter en dépendance.
release_distribution:
type: String
shortarg: r
description: Version du service applicatif a ajouter en dépendance.
response:
type: ApplicationService
description: Informations sur le service applicatif modifié.

View File

@ -0,0 +1,28 @@
---
uri: applicationservice.describe
description: |
Décrit un service applicatif.
pattern: rpc
parameters:
applicationservice_name:
type: String
shortarg: n
description: |
Nom du service applicatif à créer.
source_name:
type: String
shortarg: s
description: |
Nom de la source.
release_distribution:
type: String
shortarg: r
description: |
Version associée au service applicatif.
response:
type: ApplicationService
description: Informations sur le service applicatif.

View File

@ -0,0 +1,34 @@
---
uri: applicationservice.get_by_id
description: |
Retourne un service applicatif suivant l'identifiant.
pattern: rpc
parameters:
applicationservice_id:
type: Number
shortarg: i
description: |
ID du service applicatif à créer.
response:
type: ApplicationService
description: Informations sur le service applicatif créé.
errors:
- uri: servermodel.create.error.database_not_available
- uri: servermodel.create.error.duplicate_servermodel
- uri: servermodel.create.error.invalid_parentservermodel_id
- uri: servermodel.create.error.invalid_source_id
- uri: servermodel.create.error.unknown_parentservermodel_id
- uri: servermodel.create.error.unknown_source_id
- uri: servermodel.create.error.servermodelname_not_provided
related:
- servermodel.list
- servermodel.describe
- servermodel.update
- servermodel.delete
- servermodel.event

View File

@ -0,0 +1,10 @@
---
uri: applicationservice.updated
description: Un service application a été modifié.
pattern: event
parameters:
type: 'ApplicationService'
description: Informations sur le service applicatif modifié.

View File

@ -1,19 +1,17 @@
---
uri: config.configuration.server.deploy
description: |
Déployer la configuration d'un serveur.
description: Déployer la configuration d'un serveur.
sampleuse: ~
pattern: event
public: false
domain: config-domain
pattern: rpc
parameters:
server_id:
type: Number
description: |
Identifiant du serveur.
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: Nom du serveur.
response:
type: Deploy
description: La configuration du serveur est déployée.

View File

@ -4,20 +4,13 @@ uri: config.configuration.server.get
description: |
Récupère le contenu de la configuration.
sampleuse: ~
pattern: rpc
public: false
domain: config-domain
parameters:
server_id:
type: Number
ref: Server.ServerId
description: |
Identifiant du serveur.
server_name:
type: String
ref: Server.ServerName
description: Nom du serveur.
deployed:
type: Boolean
description: Configuration de type déployée.

View File

@ -4,19 +4,18 @@ uri: config.configuration.server.updated
description: |
Une configuration de serveur a été mise à jour.
sampleuse: ~
pattern: event
public: false
domain: config-domain
parameters:
server_id:
type: Number
description: |
Identifiant du serveur.
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: Nom du serveur.
deployed:
type: Boolean
description: Configuration de type déployée.

View File

@ -1,54 +0,0 @@
---
uri: server.create
description: |
Crée un serveur.
sampleuse: |
zephir-client server.create -s MonJoliServeur -d "un bien joli serveur" -m 1 -p MyPassPhrase
pattern: rpc
public: true
domain: server-domain
parameters:
servername:
type: String
shortarg: s
description: |
Nom du serveur.
serverdescription:
type: String
shortarg: d
description: |
Description du serveur.
servermodelid:
type: Number
shortarg: m
ref: Servermodel.ServermodelId
description: |
Identifiant du modèle de serveur.
serverpassphrase:
type: String
shortarg: p
description: |
Phrase secrète de la clef privé.
response:
type: Server
description: Description du serveur créé.
errors:
- uri: server.error.database-not-available
- uri: server.error.db-connection
- uri: server.error.invalid-servermodel-id
- uri: server.error.unknown-servermodel-id
- uri: server.error.servername-not-provided
related:
- server.list
- server.describe
- server.update
- server.delete

View File

@ -1,49 +0,0 @@
---
uri: server.describe
description: |
Retourne les attributs détaillés dun serveur.
sampleuse: |
zephir-client server.describe -s 1
pattern: rpc
public: true
domain: server-domain
parameters:
serverid:
type: Number
ref: Server.ServerId
description: Identifiant du serveur.
shortarg: s
configuration:
type: Boolean
description: Inclure les valeurs de configuration.
default: false
environment:
type: Boolean
description: Inclure les variables d'environement.
default: false
peering:
type: Boolean
description: Inclure la clé d'appairage.
default: false
response:
type: ServerDescribe
description: Description du serveur.
errors:
- uri: server.error.database-not-available
- uri: server.error.db-connection
- uri: server.error.invalid-server-id
- uri: server.error.unknown-server-id
related:
- server.list
- server.create
- server.update
- server.delete

View File

@ -1,18 +0,0 @@
---
uri: servermodel.source.list
description: |
Retourne la liste des sources.
sampleuse: |
zephir-client servermodel.source.list
pattern: rpc
public: true
domain: servermodel-domain
response:
type: '[]Dict'
description: Liste des sources disponibles.

View File

@ -0,0 +1,30 @@
---
uri: server.create
description: Crée un serveur.
pattern: rpc
parameters:
server_name:
type: String
shortarg: s
description: Nom du serveur.
server_description:
type: String
shortarg: d
description: Description du serveur.
servermodel_name:
type: String
shortarg: m
ref: Servermodel.ServermodelName
description: Nom du modèle de serveur.
release_distribution:
type: String
shortarg: r
ref: Source.ReleaseDistribution
description: Nom de la sous-version.
response:
type: Server
description: Description du serveur créé.

View File

@ -5,10 +5,6 @@ description: Un serveur a été créé.
pattern: event
public: false
domain: server-domain
parameters:
type: Server
description: Description du serveur.

View File

@ -1,17 +1,11 @@
---
uri: server.deleted
sampleuse: ~
description: |
Un serveur a été supprimé.
pattern: event
public: false
domain: server-domain
parameters:
server_id:
type: Number

View File

@ -0,0 +1,17 @@
---
uri: server.describe
description: Retourne les attributs détaillés dun serveur.
pattern: rpc
parameters:
server_name:
type: String
shortarg: s
ref: Server.ServerName
description: Nom du serveur.
response:
type: Server
description: Description du serveur.

View File

@ -4,15 +4,8 @@ uri: server.list
description: |
Liste les serveurs disponibles.
sampleuse: |
zephir-client server.list
public: true
pattern: rpc
domain: server-domain
response:
type: '[]Server'
description: Retourne la liste des serveurs.

View File

@ -0,0 +1,35 @@
---
uri: servermodel.create
description: |
Crée un modèle de serveur.
pattern: rpc
parameters:
servermodel_name:
type: String
shortarg: n
description: Nom du modèle de serveur à créer.
servermodel_description:
type: String
shortarg: d
description: |
Description du modèle de serveur à créer.
servermodel_parents_name:
type: "[]String"
ref: Servermodel.ServermodelName
shortarg: p
description: Nom des modèles de serveur parents auquels rattacher le nouveau modèle.
servermodel_parents_source_name:
type: String
shortarg: s
description: Nom de la source des modèles parents.
servermodel_parents_release_distribution:
type: String
shortarg: r
description: Nom de la distribution des modèles parents.
response:
type: Servermodel
description: Informations sur le modèle de serveur créé.

View File

@ -5,10 +5,6 @@ description: Des modèles de serveur ont été créés.
pattern: event
public: false
domain: servermodel-domain
parameters:
type: Servermodel
description: Informations sur les modèles de serveur créés.

View File

@ -0,0 +1,20 @@
uri: servermodel.dataset.updated
description: |
Initialise la table pour les modèles de serveur.
pattern: rpc
parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
release_distribution:
type: String
shortarg: r
description: Distribution de la version.
response:
type: ReturnStatus
description: Code de retour sur linjection des modèles de serveur en base.

View File

@ -4,64 +4,25 @@ uri: servermodel.describe
description: |
Retourne les attributs détaillés d'un modèle de serveur.
sampleuse: |
zephir-client servermodel.describe -s 1
pattern: rpc
public: true
domain: servermodel-domain
parameters:
servermodelid:
type: Number
servermodel_name:
type: String
shortarg: s
description: Identifiant du modèle de serveur à récupérer.
ref: Servermodel.ServermodelId
inheritance:
type: Boolean
shortarg: i
description: Inclure les données héritées des modèles de serveur parents.
default: true
resolvdepends:
type: Boolean
source_name:
type: String
shortarg: n
description: Nom de la source.
ref: Source.SourceName
release_distribution:
type: String
shortarg: r
description: Résoudre les dépendances de services.
default: true
schema:
type: Boolean
shortarg: c
description: Inclure le schema de configuration (réaggrège les données provenant du datasource).
default: false
probes:
type: Boolean
shortarg: p
description: Inclure les informations sur les sondes de la configuration.
default: false
creolefuncs:
type: Boolean
shortarg: o
description: Inclure les fonctions Creole.
default: false
conffiles:
type: Boolean
shortarg: f
description: Inclure les fichier creole au format tar encodé en base64
default: false
description: Nom de la distribution.
ref: Source.ReleaseDistribution
response:
type: Servermodel
description: Description du modèle de serveur.
errors:
- uri: servermodel.describe.error.database_not_available
- uri: servermodel.describe.error.invalid_servermodel_id
- uri: servermodel.describe.error.unknown_servermodel_id
related:
- servermodel.list
- servermodel.create
- servermodel.update
- servermodel.delete
- servermodel.event

View File

@ -4,17 +4,10 @@ uri: servermodel.list
description: |
Retourne la liste des modèles de serveur disponibles.
sampleuse: |
zephir-client servermodel.list
pattern: rpc
public: true
domain: servermodel-domain
parameters:
sourceid:
source_id:
type: Number
shortarg: s
description: |
@ -24,13 +17,3 @@ parameters:
response:
type: '[]Servermodel'
description: Liste des modèles de serveur disponibles.
errors:
- uri: servermodel.list.error.database_not_available
related:
- servermodel.describe
- servermodel.create
- servermodel.update
- servermodel.delete
- servermodel.event

View File

@ -5,10 +5,6 @@ description: Des modèles de serveur ont été modifiés.
pattern: event
public: false
domain: servermodel-domain
parameters:
type: 'Servermodel'
description: Informations sur les modèles de serveur modifiés.

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
response:
type: '[]Session'
description: |

View File

@ -6,17 +6,12 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
id:
type: Number
ref: Server.ServerId
shortarg: c
description: |
Identifiant de la configuration.
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: Nom du serveur.
response:
type: Session

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
ref: Config.SessionId

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
ref: Config.SessionId

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String

View File

@ -5,10 +5,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
response:
type: '[]Session'
description: |

View File

@ -5,17 +5,12 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
id:
type: Number
ref: Servermodel.ServermodelId
shortarg: c
description: |
Identifiant de la configuration.
servermodel_name:
type: String
ref: Servermodel.ServermodelName
shortarg: s
description: Nom du serveurmodel.
response:
type: Session

View File

@ -5,10 +5,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
ref: Config.SessionId

View File

@ -6,10 +6,6 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
ref: Config.SessionId

View File

@ -0,0 +1,23 @@
---
uri: source.create
description: |
Créer une source.
pattern: rpc
parameters:
source_name:
type: String
shortarg: n
description: |
Nom de la source.
source_url:
type: String
shortarg: u
description: |
URL de téléchargement de la source.
response:
type: 'Source'
description: Information sur la source.

View File

@ -0,0 +1,20 @@
uri: source.dataset.update
description: |
Initialise la table pour les versions.
pattern: rpc
parameters:
source_id:
type: Number
shortarg: s
description: ID de la source.
release_name:
type: String
shortarg: r
description: Nom de la version.
response:
type: Release
description: Informations sur la version injectée en base.

View File

@ -0,0 +1,18 @@
---
uri: source.describe
description: |
Retourne une source.
pattern: rpc
parameters:
source_name:
type: String
shortarg: n
description: |
Nom de la source.
response:
type: 'Source'
description: Information sur la source.

View File

@ -0,0 +1,11 @@
---
uri: source.list
description: |
Retourne la liste des sources.
pattern: rpc
response:
type: '[]Source'
description: Liste des sources disponibles.

View File

@ -0,0 +1,27 @@
---
uri: source.release.create
description: |
Créer une version.
pattern: rpc
parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
release_name:
type: String
shortarg: n
description: |
Nom de la version.
release_distribution:
type: String
shortarg: d
description: |
Distribution de la version.
response:
type: 'Release'
description: Information sur la version.

View File

@ -0,0 +1,21 @@
---
uri: source.release.describe
description: |
Retourne la sous-version.
pattern: rpc
parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
release_distribution:
type: String
shortarg: r
description: Nom de la sous-version
response:
type: 'Release'
description: Sous-version.

View File

@ -0,0 +1,17 @@
---
uri: source.release.get_by_id
description: |
Retourne version suivant l'identifiant.
pattern: rpc
parameters:
release_id:
type: Number
shortarg: r
description: ID de la version.
response:
type: 'Release'
description: La version disponibles.

View File

@ -0,0 +1,17 @@
---
uri: source.release.list
description: |
Retourne la liste des versions.
pattern: rpc
parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
response:
type: '[]Release'
description: Liste des versions disponibles.

View File

@ -4,21 +4,14 @@ uri: template.generate
description: |
Génère et récupère les templates générés.
sampleuse: ~
pattern: rpc
public: true
domain: template-domain
parameters:
server_id:
type: Number
ref: Server.ServerId
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: |
Identifiant du serveur.
description: Nom du serveur.
response:
type: Template

View File

@ -0,0 +1,22 @@
---
uri: uri.role.join
description: Crée un rôle utilisateur.
pattern: rpc
parameters:
role_name:
type: String
shortarg: r
description: Nom du rôle.
ref: User.RoleName
uri_name:
type: String
shortarg: u
description: Nom du message.
response:
type: URIRole
description: Association de rôle créé.

View File

@ -0,0 +1,11 @@
---
uri: uri.role.list
description: Liste des associations d'URI et de rôle.
pattern: rpc
response:
type: '[]URIRole'
description: Liste des associations.

View File

@ -0,0 +1,25 @@
---
uri: user.create
description: Crée un utilisateur.
pattern: rpc
parameters:
user_login:
type: String
shortarg: l
description: Login de l'utilisateur.
ref: User.Login
user_name:
type: String
shortarg: n
description: Nom de l'utilisateur.
user_surname:
type: String
shortarg: s
description: Nom de famille de l'utilisateur.
response:
type: User
description: Description de l'utilisateur créé.

View File

@ -0,0 +1,18 @@
---
uri: user.delete
description: Supprimer un utilisateur.
pattern: rpc
parameters:
user_login:
type: String
shortarg: l
description: Login de l'utilisateur.
ref: User.Login
response:
type: User
description: Description de l'utilisateur supprimé.

View File

@ -0,0 +1,11 @@
---
uri: user.list
description: |
Liste les utilisateurs disponibles.
pattern: rpc
response:
type: '[]User'
description: Retourne la liste des utilisateurs.

View File

@ -0,0 +1,32 @@
---
uri: user.role.create
description: Crée un rôle utilisateur.
pattern: rpc
parameters:
user_login:
type: String
shortarg: u
description: Login de l'utilisateur.
ref: User.UserLogin
role_name:
type: String
shortarg: n
description: Nom du rôle.
ref: User.RoleName
role_attribute:
type: String
shortarg: a
description: Attribut contrôlé.
default: null
role_attribute_value:
type: String
shortarg: v
description: Valeur de l'attribut contrôlé.
default: null
response:
type: Role
description: Description du rôle créé.

View File

@ -0,0 +1,19 @@
---
uri: user.role.list
description: |
Liste les rôles disponibles.
pattern: rpc
parameters:
user_login:
type: String
shortarg: l
description: Login de l'utilisateur associé.
ref: User.RoleName
default: null
response:
type: '[]Role'
description: Retourne la liste des rôles.

View File

@ -0,0 +1,28 @@
---
title: ApplicationService
type: object
description: Description d'un modèle de serveur.
properties:
applicationservice_id:
type: number
description: ID du service applicatif.
applicationservice_name:
type: string
description: Nom du service applicatif.
applicationservice_description:
type: string
description: Description du service applicatif.
applicationservice_release_id:
type: number
ref: Version.ReleaseId
description: Version du service applicatif.
applicationservice_dependencies:
type: array
items:
type: number
description: Liste des services applicatifs déclarés en dépendance de ce service applicatif.
required:
- applicationservice_id
- applicationservice_name
- applicationservice_release_id

View File

@ -3,10 +3,10 @@ title: ConfigConfiguration
type: object
description: Description de la configuration.
properties:
server_id:
type: number
description: Identifiant du serveur.
ref: Server.ServerId
server_name:
type: string
description: Nom du serveur.
ref: Server.ServerName
deployed:
type: boolean
description: La configuration est déployée.

View File

@ -0,0 +1,20 @@
---
title: Deploy
type: object
description: État de déploiement de la configuration.
properties:
server_id:
type: number
description: ID du serveur.
ref: Server.ServerID
server_name:
type: string
ref: Server.ServerName
description: Nom du server.
deployed:
type: boolean
description: État de déploiement.
required:
- server_id
- server_name
- deployed

View File

@ -0,0 +1,14 @@
---
title: ReturnStatus
type: object
description: Résultat dune commande.
properties:
retcode:
type: number
description: Code de retour de la commande.
returns:
type: string
description: Retour de la commande.
required:
- retcode
- returns

View File

@ -0,0 +1,28 @@
---
title: Release
type: object
description: Description de la version.
properties:
release_id:
type: number
description: Identifiant de la version.
release_name:
type: string
description: Le nom de la version.
release_distribution:
type: string
description: Le nom de la distribution de la version.
source_url:
type: string
description: URL de la source.
ref: Source.ReleaseId
source_name:
type: string
description: Le nom de la source.
required:
- release_id
- release_name
- release_distribution
- source_name
- source_url

View File

@ -0,0 +1,26 @@
---
title: Role
type: object
description: Description du rôle.
properties:
role_id:
type: number
description: Identifiant de l'utilisateur.
ref: User.RoleUserId
user_login:
type: string
description: Login du l'utilisateur.
ref: User.Login
role_name:
type: string
description: Nom du rôle.
role_attribute:
type: string
description: Nom de l'utilisateur.
role_attribute_value:
type: string
description: Valeur de l'attribut contrôlé.
required:
- role_id
- role_name

View File

@ -1,44 +0,0 @@
---
title: ServerDescribe
type: object
description: Description du serveur.
properties:
serverid:
type: number
description: Identifiant du serveur.
ref: Server.ServerId
servername:
type: string
description: Nom du serveur.
serverdescription:
type: string
description: Description du serveur.
servermodelid:
type: number
description: Identifiant du modèle de serveur.
ref: Servermodel.ServermodelId
zoneid:
type: number
description: Identifiant de la zone.
ref: Zone.ZoneId
machineid:
type: number
description: Identifiant de la machine.
ref: Zone.MachineId
configuration:
type: file
description: Valeurs de configuration.
serverenvironment:
type: object
description: Variables d'environnement du serveur.
peering:
type: object
description: Clé d'appairage.
lastpeerconnection:
type: string
description: Timestamp de la dernière connexion avec le serveur.
required:
- serverid
- servername
- serverdescription
- servermodelid

View File

@ -7,33 +7,18 @@ properties:
type: number
description: Identifiant du serveur.
ref: Server.ServerId
servername:
server_name:
type: string
description: Nom du serveur.
serverdescription:
server_description:
type: string
description: Description du serveur.
servermodelid:
server_servermodel_id:
type: number
description: Identifiant du modèle de serveur.
ref: Servermodel.ServermodelId
zoneid:
type: number
description: Identifiant de la zone.
ref: Zone.ZoneId
machineid:
type: number
description: Identifiant de la machine.
ref: Zone.MachineId
automation:
type: string
description: Moteur d'exécution.
ref: Server.automation
lastpeerconnection:
type: string
description: Timestamp de la dernière connexion avec le serveur.
required:
- server_id
- servername
- serverdescription
- servermodelid
- server_name
- server_description
- server_servermodel_id

View File

@ -3,16 +3,16 @@ title: ServermodelSource
type: object
description: Description de la source.
properties:
sourceid:
source_id:
type: number
description: ID de la source.
sourcename:
source_name:
type: string
description: Nom de la source.
sourceurl:
source_url:
type: string
description: URL de la source.
required:
- sourceid
- sourcename
- sourceurl
- source_id
- source_name
- source_url

View File

@ -3,55 +3,33 @@ title: Servermodel
type: object
description: Description d'un modèle de serveur.
properties:
servermodelid:
servermodel_id:
type: number
description: ID du modèle de serveur.
ref: Servermodel.ServermodelId
servermodelname:
servermodel_name:
type: string
description: Nom du modèle de serveur.
servermodeldescription:
servermodel_description:
type: string
description: Description du modèle de serveur.
servermodelparentsid:
servermodel_parents_id:
type: array
items:
type: number
ref: Servermodel.ServermodelId
description: ID du modèle de serveur parent.
subreleaseid:
release_id:
type: number
ref: Servermodel.SubreleaseId
description: Version du modèle de serveur.
subreleasename:
type: string
ref: SubRelease.SubReleaseName
description: Nom de la sous-version.
sourceid:
servermodel_applicationservice_id:
type: number
ref: ServermodelId.SourceId
description: ID de la sous-version.
services:
type: array
items:
type: object
description: Liste des services applicatifs déclarés pour ce modèle de serveur.
schema:
type: string
description: Contenu du schema.
probes:
type: string
description: Informations sur les sondes.
creolefuncs:
type: string
description: Fonctions Creole.
conffiles:
type: string
description: Fichiers creole au format tar encodé base64
ref: Applicationservice.ApplicationserviceId
description: Identifiant de l'application service local.
required:
- servermodelid
- servermodelname
- servermodeldescription
- servermodelsubreleaseid
- sourceid
- subreleasename
- servermodel_id
- servermodel_name
- servermodel_description
- release_id
- servermodel_applicationservice_id

View File

@ -0,0 +1,20 @@
---
title: Source
type: object
description: Description d'un source.
properties:
source_id:
type: number
description: ID de la source.
ref: Source.SourceId
source_name:
type: string
description: Nom de la source.
source_url:
type: string
description: URL de téléchargement de la source.
required:
- source_id
- source_name
- source_url

View File

@ -3,12 +3,13 @@ title: Template
type: object
description: Les fichiers de configuration générés.
properties:
server_id:
type: Number
description: Identifiant du serveur.
server_name:
type: String
description: Nom du serveur.
ref: Server.ServerName
template_dir:
type: String
description: Nom du répertoire avec les fichiers de configuration générés.
required:
- server_id
- server_name
- template_dir

View File

@ -0,0 +1,15 @@
---
title: URIRole
type: object
description: Description de l'assication du message et du rôle.
properties:
role_name:
type: string
description: Nom du rôle
ref: User.RoleName
uri_name:
type: string
description: Nom du message
required:
- role_name
- uri_name

View File

@ -0,0 +1,24 @@
---
title: User
type: object
description: Description de l'utilisateur.
properties:
user_id:
type: number
description: Identifiant de l'utilisateur.
ref: User.UserId
user_login:
type: string
description: Login de l'utilisateur.
user_name:
type: string
description: Nom de l'utilisateur.
user_surname:
type: string
description: Nom de famille de l'utilisateur.
required:
- user_id
- user_login
- user_name
- user_surname

View File

@ -0,0 +1,20 @@
---
title: UserRole
type: object
description: Description de l'association du rôle et de l'utilisateur.
properties:
user_role_id:
type: number
description: Identifiant de l'association.
ref: User.UserRoleId
user_login:
type: string
description: Login de l'utilisateur.
role_name:
type: string
description: Nom du rôle.
required:
- user_role_id
- user_login
- role_name

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
asyncpg==0.20.1
lxml==4.4.2
requests==2.22.0
aiohttp==3.6.2
pytest==5.3.3
PyYAML==5.3
tiramisu==3.0rc15

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

110
script/database_manager.py Normal file
View File

@ -0,0 +1,110 @@
import asyncpg
import asyncio
from risotto.config import get_config
VERSION_INIT = """
-- Source
CREATE TABLE Source (
SourceId SERIAL PRIMARY KEY,
SourceName VARCHAR(255) NOT NULL UNIQUE,
SourceURL TEXT
);
-- Release
CREATE TABLE Release (
ReleaseId SERIAL PRIMARY KEY,
ReleaseName VARCHAR(255) NOT NULL,
ReleaseSourceId INTEGER NOT NULL,
ReleaseDistribution VARCHAR(20) CONSTRAINT releasedistribution_choice CHECK (ReleaseDistribution IN ('last', 'n-1', 'n-2')),
UNIQUE (ReleaseName, ReleaseSourceId),
UNIQUE (ReleaseDistribution, ReleaseSourceId),
FOREIGN KEY (ReleaseSourceId) REFERENCES Source(SourceId)
);
-- Servermodel
CREATE TABLE Servermodel (
ServermodelId SERIAL PRIMARY KEY,
ServermodelName VARCHAR(255) NOT NULL,
ServermodelDescription VARCHAR(255) NOT NULL,
ServermodelParentsId INTEGER [] DEFAULT '{}',
ServermodelReleaseId INTEGER NOT NULL,
ServermodelApplicationserviceId INTEGER NOT NULL,
UNIQUE (ServermodelName, ServermodelReleaseId)
);
CREATE INDEX ServermodelApplicationserviceId_index ON Servermodel (ServermodelApplicationserviceId);
-- Applicationservice
CREATE TABLE Applicationservice (
ApplicationserviceId SERIAL PRIMARY KEY,
ApplicationserviceName VARCHAR(255) NOT NULL,
ApplicationserviceDescription VARCHAR(255) NOT NULL,
ApplicationserviceReleaseId INTEGER NOT NULL,
UNIQUE (ApplicationserviceName, ApplicationserviceReleaseId)
);
CREATE TABLE ApplicationserviceDependency (
ApplicationserviceId INTEGER NOT NULL,
ApplicationserviceDependencyId INTEGER NOT NULL,
UNIQUE(ApplicationserviceId, ApplicationserviceDependencyId),
FOREIGN KEY (ApplicationserviceId) REFERENCES Applicationservice(ApplicationserviceId),
FOREIGN KEY (ApplicationserviceDependencyId) REFERENCES Applicationservice(ApplicationserviceId)
);
-- Server
CREATE TABLE Server (
ServerId SERIAL PRIMARY KEY,
ServerName VARCHAR(255) NOT NULL UNIQUE,
ServerDescription VARCHAR(255) NOT NULL,
ServerServermodelId INTEGER NOT NULL
);
-- User, Role and ACL
CREATE TABLE RisottoUser (
UserId SERIAL PRIMARY KEY,
UserLogin VARCHAR(100) NOT NULL UNIQUE,
UserName VARCHAR(100) NOT NULL,
UserSurname VARCHAR(100) NOT NULL
);
CREATE TABLE UserRole (
RoleId SERIAL PRIMARY KEY,
RoleUserId INTEGER NOT NULL,
RoleName VARCHAR(255) NOT NULL,
RoleAttribute VARCHAR(255),
RoleAttributeValue VARCHAR(255),
FOREIGN KEY (RoleUserId) REFERENCES RisottoUser(UserId)
);
CREATE TABLE URI (
URIId SERIAL PRIMARY KEY,
URIName VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE RoleURI (
RoleName VARCHAR(255) NOT NULL,
URIId INTEGER NOT NULL,
FOREIGN KEY (URIId) REFERENCES URI(URIId),
PRIMARY KEY (RoleName, URIId)
);
-- Log
CREATE TABLE log(
Msg VARCHAR(255) NOT NULL,
Level VARCHAR(10) NOT NULL,
Path VARCHAR(255),
Username VARCHAR(100) NOT NULL,
Data JSON,
Date timestamp DEFAULT current_timestamp
);
"""
async def main():
db_conf = get_config()['database']['dsn']
pool = await asyncpg.create_pool(db_conf)
async with pool.acquire() as connection:
async with connection.transaction():
returns = await connection.execute(VERSION_INIT)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# asyncio.run(main())

View File

@ -1,6 +1,7 @@
from asyncio import get_event_loop
from risotto import get_app
if __name__ == '__main__':
loop = get_event_loop()
loop.run_until_complete(get_app(loop))
@ -8,5 +9,3 @@ if __name__ == '__main__':
loop.run_forever()
except KeyboardInterrupt:
pass

View File

@ -1,10 +1,32 @@
HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages'
ROOT_CACHE_DIR = 'cache'
DEBUG = False
DATABASE_DIR = 'database'
INTERNAL_USER = 'internal'
CONFIGURATION_DIR = 'configurations'
TEMPLATE_DIR = 'templates'
TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
DEFAULT_USER = 'Anonymous'
DEFAULT_DSN = 'postgres:///risotto?host=/var/run/postgresql/&user=risotto'
DEFAULT_TIRAMISU_DSN = 'postgres:///tiramisu?host=/var/run/postgresql/&user=tiramisu'
from os import environ
from pathlib import PurePosixPath
CURRENT_PATH = PurePosixPath(__file__)
def get_config():
return {'database': {'dsn': environ.get('RISOTTO_DSN', DEFAULT_DSN),
'tiramisu_dsn': environ.get('RISOTTO_TIRAMISU_DSN', DEFAULT_TIRAMISU_DSN),
},
'http_server': {'port': 8080,
#'default_user': "gnunux"},
'default_user': DEFAULT_USER},
'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages',
'debug': True,
'internal_user': 'internal',
'check_role': True,
'admin_user': DEFAULT_USER},
'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'},
'servermodel': {'internal_source': 'internal',
'internal_distribution': 'last',
'internal_release_name': 'none'},
}

View File

@ -5,6 +5,10 @@ from .context import Context
class Controller:
"""Common controller used to add a service in Risotto
"""
def __init__(self,
test: bool):
pass
async def call(self,
uri: str,
risotto_context: Context,

View File

@ -1,78 +1,73 @@
from tiramisu import Config
from traceback import print_exc
from copy import copy
from typing import Dict, Callable
from typing import Dict, Callable, List, Optional
from json import dumps, loads
from .utils import _
from .error import CallError, NotAllowedError
from .logger import log
from .config import DEBUG
from .config import get_config
from .context import Context
from . import register
import asyncpg
class CallDispatcher:
def valid_public_function(self,
risotto_context: Context,
kwargs: Dict,
public_only: bool):
if public_only and not self.messages[risotto_context.version][risotto_context.message]['public']:
msg = _(f'the message {risotto_context.message} is private')
log.error_msg(risotto_context, kwargs, msg)
raise NotAllowedError(msg)
def valid_call_returns(self,
risotto_context: Context,
returns: Dict,
kwargs: Dict):
async def valid_call_returns(self,
risotto_context: Context,
function,
returns: Dict,
kwargs: Dict):
response = self.messages[risotto_context.version][risotto_context.message]['response']
module_name = risotto_context.function.__module__.split('.')[-2]
function_name = risotto_context.function.__name__
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
if response.impl_get_information('multi'):
if not isinstance(returns, list):
err = _(f'function {module_name}.{function_name} has to return a list')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
else:
if not isinstance(returns, dict):
await log.error_msg(risotto_context, kwargs, returns)
err = _(f'function {module_name}.{function_name} has to return a dict')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
returns = [returns]
if response is None:
raise Exception('hu?')
else:
for ret in returns:
config = Config(response, display_name=lambda self, dyn_name: self.impl_getname())
config.property.read_write()
try:
for key, value in ret.items():
config.option(key).value.set(value)
except AttributeError:
err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}"')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
except ValueError:
err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}"')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
config.property.read_only()
mandatories = list(config.value.mandatory())
if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories]
raise ValueError(_(f'missing parameters in response: {mand} in message "{risotto_context.message}"'))
try:
config.value.dict()
except Exception as err:
err = _(f'function {module_name}.{function_name} return an invalid response {err}')
log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
async with await Config(response, display_name=lambda self, dyn_name: self.impl_getname()) as config:
await config.property.read_write()
try:
for key, value in ret.items():
await config.option(key).value.set(value)
except AttributeError:
err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}"')
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
except ValueError:
err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}"')
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
await config.property.read_only()
mandatories = await config.value.mandatory()
if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories]
raise ValueError(_(f'missing parameters in response: {mand} in message "{risotto_context.message}"'))
try:
await config.value.dict()
except Exception as err:
err = _(f'function {module_name}.{function_name} return an invalid response {err}')
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
async def call(self,
version: str,
message: str,
old_risotto_context: Context,
public_only: bool=False,
check_role: bool=False,
**kwargs):
""" execute the function associate with specified uri
arguments are validate before
@ -81,103 +76,107 @@ class CallDispatcher:
version,
message,
'rpc')
self.valid_public_function(risotto_context,
kwargs,
public_only)
self.check_message_type(risotto_context,
kwargs)
try:
tiramisu_config = self.load_kwargs_to_config(risotto_context,
kwargs)
obj = self.messages[version][message]
kw = tiramisu_config.option(message).value.dict()
risotto_context.function = obj['function']
if obj['risotto_context']:
kw['risotto_context'] = risotto_context
returns = await risotto_context.function(self.injected_self[obj['module']], **kw)
except CallError as err:
raise err
except Exception as err:
if DEBUG:
print_exc()
log.error_msg(risotto_context,
kwargs,
err)
raise CallError(str(err))
# valid returns
self.valid_call_returns(risotto_context,
returns,
kwargs)
# log the success
log.info_msg(risotto_context,
kwargs,
_(f'returns {returns}'))
# notification
if obj.get('notification'):
notif_version, notif_message = obj['notification'].split('.', 1)
if not isinstance(returns, list):
send_returns = [returns]
else:
send_returns = returns
for ret in send_returns:
await self.publish(notif_version,
notif_message,
risotto_context,
**ret)
return returns
function_objs = [self.messages[version][message]]
# do not start a new database connection
if hasattr(old_risotto_context, 'connection'):
risotto_context.connection = old_risotto_context.connection
return await self.launch(version,
message,
risotto_context,
check_role,
kwargs,
function_objs)
else:
try:
async with self.pool.acquire() as connection:
await connection.set_type_codec(
'json',
encoder=dumps,
decoder=loads,
schema='pg_catalog'
)
risotto_context.connection = connection
async with connection.transaction():
return await self.launch(version,
message,
risotto_context,
check_role,
kwargs,
function_objs)
except CallError as err:
raise err
except Exception as err:
# if there is a problem with arguments, just send an error and do nothing
if get_config()['global']['debug']:
print_exc()
async with self.pool.acquire() as connection:
await connection.set_type_codec(
'json',
encoder=dumps,
decoder=loads,
schema='pg_catalog'
)
risotto_context.connection = connection
async with connection.transaction():
await log.error_msg(risotto_context, kwargs, err)
raise err
class PublishDispatcher:
async def publish(self, version, message, old_risotto_context, public_only=False, **kwargs):
async def publish(self,
version: str,
message: str,
old_risotto_context: Context,
check_role: bool=False,
**kwargs) -> None:
risotto_context = self.build_new_context(old_risotto_context,
version,
message,
'event')
self.check_message_type(risotto_context,
kwargs)
try:
config = self.load_kwargs_to_config(risotto_context,
kwargs)
config_arguments = config.option(message).value.dict()
except CallError as err:
return
except Exception as err:
# if there is a problem with arguments, just send an error et do nothing
if DEBUG:
print_exc()
log.error_msg(risotto_context, kwargs, err)
return
# config is ok, so publish the message
for function_obj in self.messages[version][message].get('functions', []):
function = function_obj['function']
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
info_msg = _(f'in module {module_name}.{function_name}')
function_objs = self.messages[version][message].get('functions', [])
# do not start a new database connection
if hasattr(old_risotto_context, 'connection'):
risotto_context.connection = old_risotto_context.connection
return await self.launch(version,
message,
risotto_context,
check_role,
kwargs,
function_objs)
else:
try:
# build argument for this function
kw = {}
for key, value in config_arguments.items():
if key in function_obj['arguments']:
kw[key] = value
if function_obj['risotto_context']:
kw['risotto_context'] = risotto_context
# send event
returns = await function(self.injected_self[function_obj['module']], **kw)
async with self.pool.acquire() as connection:
await connection.set_type_codec(
'json',
encoder=dumps,
decoder=loads,
schema='pg_catalog'
)
risotto_context.connection = connection
async with connection.transaction():
return await self.launch(version,
message,
risotto_context,
check_role,
kwargs,
function_objs)
except CallError as err:
raise err
except Exception as err:
if DEBUG:
# if there is a problem with arguments, just send an error and do nothing
if get_config()['global']['debug']:
print_exc()
log.error_msg(risotto_context, kwargs, err, info_msg)
continue
else:
log.info_msg(risotto_context, kwargs, info_msg)
# notification
if function_obj.get('notification'):
notif_version, notif_message = function_obj['notification'].split('.', 1)
await self.publish(notif_version,
notif_message,
risotto_context,
**returns)
async with self.pool.acquire() as connection:
await connection.set_type_codec(
'json',
encoder=dumps,
decoder=loads,
schema='pg_catalog'
)
risotto_context.connection = connection
async with connection.transaction():
await log.error_msg(risotto_context, kwargs, err)
raise err
class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher):
@ -202,46 +201,172 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
risotto_context.version = version
return risotto_context
def check_message_type(self,
risotto_context: Context,
kwargs: Dict):
async def check_message_type(self,
risotto_context: Context,
kwargs: Dict):
if self.messages[risotto_context.version][risotto_context.message]['pattern'] != risotto_context.type:
msg = _(f'{risotto_context.uri} is not a {risotto_context.type} message')
log.error_msg(risotto_context, kwargs, msg)
await log.error_msg(risotto_context, kwargs, msg)
raise CallError(msg)
def load_kwargs_to_config(self,
risotto_context: Context,
kwargs: Dict):
async def load_kwargs_to_config(self,
risotto_context: Context,
uri: str,
kwargs: Dict,
check_role: bool):
""" create a new Config et set values to it
"""
# create a new config
config = Config(self.option)
config.property.read_write()
# set message's option
config.option('message').value.set(risotto_context.message)
# store values
subconfig = config.option(risotto_context.message)
for key, value in kwargs.items():
try:
subconfig.option(key).value.set(value)
except AttributeError:
if DEBUG:
print_exc()
raise AttributeError(_(f'unknown parameter "{key}"'))
# check mandatories options
config.property.read_only()
mandatories = list(config.value.mandatory())
if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories]
raise ValueError(_(f'missing parameters: {mand}'))
# return the config
return config
async with await Config(self.option) as config:
await config.property.read_write()
# set message's option
await config.option('message').value.set(risotto_context.message)
# store values
subconfig = config.option(risotto_context.message)
for key, value in kwargs.items():
try:
await subconfig.option(key).value.set(value)
except AttributeError:
if get_config()['global']['debug']:
print_exc()
raise ValueError(_(f'unknown parameter in "{uri}": "{key}"'))
# check mandatories options
if check_role and get_config().get('global').get('check_role'):
await self.check_role(subconfig,
risotto_context.username,
uri)
await config.property.read_only()
mandatories = await config.value.mandatory()
if mandatories:
mand = [mand.split('.')[-1] for mand in mandatories]
raise ValueError(_(f'missing parameters in "{uri}": {mand}'))
# return complete an validated kwargs
return await subconfig.value.dict()
def get_service(self,
name: str):
return self.injected_self[name]
async def check_role(self,
config: Config,
user_login: str,
uri: str) -> None:
async with self.pool.acquire() as connection:
async with connection.transaction():
# Verify if user exists and get ID
sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
user_id = await connection.fetchval(sql,
user_login)
if user_id is None:
raise NotAllowedError(_(f"You ({user_login}) don't have any account in this application"))
# Get all references for this message
refs = {}
for option in await config.list('all'):
ref = await option.information.get('ref')
if ref:
refs[ref] = str(await option.value.get())
# Check role
select_role_uri = '''
SELECT RoleName
FROM URI, RoleURI
WHERE URI.URIName = $1 AND RoleURI.URIId = URI.URIId
'''
select_role_user = '''
SELECT RoleAttribute, RoleAttributeValue
FROM UserRole
WHERE RoleUserId = $1 AND RoleName = $2
'''
for uri_role in await connection.fetch(select_role_uri,
uri):
for user_role in await connection.fetch(select_role_user,
user_id,
uri_role['rolename']):
if not user_role['roleattribute']:
return
if user_role['roleattribute'] in refs and \
user_role['roleattributevalue'] == refs[user_role['roleattribute']]:
return
raise NotAllowedError(_(f'You ({user_login}) don\'t have any authorisation to access to "{uri}"'))
async def launch(self,
version: str,
message: str,
risotto_context: Context,
check_role: bool,
kwargs: Dict,
function_objs: List) -> Optional[Dict]:
await self.check_message_type(risotto_context,
kwargs)
config_arguments = await self.load_kwargs_to_config(risotto_context,
f'{version}.{message}',
kwargs,
check_role)
# config is ok, so send the message
for function_obj in function_objs:
function = function_obj['function']
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
info_msg = _(f'in module {module_name}.{function_name}')
try:
# build argument for this function
if risotto_context.type == 'rpc':
kw = config_arguments
else:
kw = {}
for key, value in config_arguments.items():
if key in function_obj['arguments']:
kw[key] = value
kw['risotto_context'] = risotto_context
returns = await function(self.injected_self[function_obj['module']], **kw)
except CallError as err:
if risotto_context.type == 'rpc':
raise err
continue
except Exception as err:
if risotto_context.type == 'rpc':
raise err
if get_config().get('global').get('debug'):
print_exc()
await log.error_msg(risotto_context,
kwargs,
err)
continue
else:
if risotto_context.type == 'rpc':
# valid returns
await self.valid_call_returns(risotto_context,
function,
returns,
kwargs)
# log the success
await log.info_msg(risotto_context,
{'arguments': kwargs,
'returns': returns},
info_msg)
# notification
if function_obj.get('notification'):
notif_version, notif_message = function_obj['notification'].split('.', 1)
if not isinstance(returns, list):
send_returns = [returns]
else:
send_returns = returns
for ret in send_returns:
await self.publish(notif_version,
notif_message,
risotto_context,
**ret)
if risotto_context.type == 'rpc':
return returns
dispatcher = Dispatcher()
register.dispatcher = dispatcher

View File

@ -8,3 +8,7 @@ class CallError(Exception):
class NotAllowedError(Exception):
pass
class ExecutionError(Exception):
pass

View File

@ -1,7 +1,7 @@
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
from json import dumps
from traceback import print_exc
from tiramisu import Config
from tiramisu import Config, default_storage
from .dispatcher import dispatcher
@ -10,13 +10,14 @@ from .context import Context
from .error import CallError, NotAllowedError, RegistrationError
from .message import get_messages
from .logger import log
from .config import DEBUG, HTTP_PORT
from .config import get_config
from .services import load_services
def create_context(request):
risotto_context = Context()
risotto_context.username = request.match_info.get('username', "Anonymous")
risotto_context.username = request.match_info.get('username',
get_config()['http_server']['default_user'])
return risotto_context
@ -52,30 +53,35 @@ class extra_route_handler:
except CallError as err:
raise HTTPBadRequest(reason=str(err))
except Exception as err:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise HTTPInternalServerError(reason=str(err))
log.info_msg(kwargs['risotto_context'],
dict(request.match_info))
# await log.info_msg(kwargs['risotto_context'],
# dict(request.match_info))
return Response(text=dumps(returns))
async def handle(request):
version, uri = request.match_info.get_info()['path'].rsplit('/', 2)[-2:]
version, message = request.match_info.get_info()['path'].rsplit('/', 2)[-2:]
risotto_context = create_context(request)
kwargs = await request.json()
try:
text = await dispatcher.call(version,
uri,
risotto_context,
public_only=True,
**kwargs)
pattern = dispatcher.messages[version][message]['pattern']
if pattern == 'rpc':
method = dispatcher.call
else:
method = dispatcher.publish
text = await method(version,
message,
risotto_context,
check_role=True,
**kwargs)
except NotAllowedError as err:
raise HTTPNotFound(reason=str(err))
except CallError as err:
raise HTTPBadRequest(reason=str(err))
raise HTTPBadRequest(reason=str(err).replace('\n', ' '))
except Exception as err:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise HTTPInternalServerError(reason=str(err))
return Response(text=dumps({'response': text}))
@ -84,10 +90,21 @@ async def handle(request):
async def api(request, risotto_context):
global tiramisu
if not tiramisu:
config = Config(get_messages(load_shortarg=True,
only_public=True)[1])
config.property.read_write()
tiramisu = config.option.dict(remotable='none')
# check all URI that have an associated role
# all URI without role is concidered has a private URI
uris = []
async with dispatcher.pool.acquire() as connection:
async with connection.transaction():
# Check role with ACL
sql = '''
SELECT URI.URIName
FROM URI, RoleURI
WHERE RoleURI.URIId = URI.URIId
'''
uris = [uri['uriname'] for uri in await connection.fetch(sql)]
async with await Config(get_messages(load_shortarg=True, uris=uris)[1]) as config:
await config.property.read_write()
tiramisu = await config.option.dict(remotable='none')
return tiramisu
@ -102,16 +119,15 @@ async def get_app(loop):
load_services()
app = Application(loop=loop)
routes = []
default_storage.engine('dictionary')
await dispatcher.load()
for version, messages in dispatcher.messages.items():
print()
print(_('======== Registered messages ========'))
for message in messages:
web_message = f'/api/{version}/{message}'
if dispatcher.messages[version][message]['public']:
print(f' - {web_message}')
else:
pattern = dispatcher.messages[version][message]['pattern']
print(f' - {web_message} (private {pattern})')
pattern = dispatcher.messages[version][message]['pattern']
print(f' - {web_message} ({pattern})')
routes.append(post(web_message, handle))
print()
print(_('======== Registered extra routes ========'))
@ -127,7 +143,7 @@ async def get_app(loop):
del extra_routes
app.add_routes(routes)
await dispatcher.on_join()
return await loop.create_server(app.make_handler(), '*', HTTP_PORT)
return await loop.create_server(app.make_handler(), '*', get_config()['http_server']['port'])
tiramisu = None

View File

@ -1,13 +1,30 @@
from typing import Dict
from typing import Dict, Any
from json import dumps
from .context import Context
from .utils import _
from .config import DEBUG
from .config import get_config
class Logger:
""" An object to manager log
FIXME should add event to a database
"""
async def insert(self,
msg: str,
path: str,
risotto_context: str,
level: str,
data: Any= None) -> None:
insert = 'INSERT INTO log(Msg, Path, Username, Level'
values = 'VALUES($1,$2,$3,$4'
args = [msg, path, risotto_context.username, level]
if data:
insert += ', Data'
values += ',$5'
args.append(dumps(data))
sql = insert + ') ' + values + ')'
await risotto_context.connection.fetch(sql, *args)
def _get_message_paths(self,
risotto_context: Context):
paths = risotto_context.paths
@ -20,38 +37,50 @@ class Logger:
else:
paths_msg += f'sub-messages: '
paths_msg += ' > '.join(paths)
paths_msg += ':'
return paths_msg
def error_msg(self,
risotto_context: Context,
arguments,
error: str,
msg: str=''):
async def error_msg(self,
risotto_context: Context,
arguments,
error: str,
msg: str=''):
""" send message when an error append
"""
paths_msg = self._get_message_paths(risotto_context)
# if DEBUG:
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})'))
await self.insert(msg,
paths_msg,
risotto_context,
'Error',
arguments)
def info_msg(self,
risotto_context: Context,
arguments: Dict,
msg: str=''):
async def info_msg(self,
risotto_context: Context,
arguments: Dict,
msg: str=''):
""" send message with common information
"""
if risotto_context.paths:
paths_msg = self._get_message_paths(risotto_context)
else:
paths_msg = ''
tmsg = _(f'{risotto_context.username}: INFO:{paths_msg}')
if arguments:
tmsg += _(f' with arguments "{arguments}"')
if msg:
tmsg += f' {msg}'
if get_config()['global']['debug']:
print(_(f'{risotto_context.username}: INFO:{paths_msg}'))
await self.insert(msg,
paths_msg,
risotto_context,
'Info',
arguments)
if DEBUG:
print(tmsg)
async def info(self,
risotto_context,
msg):
if get_config()['global']['debug']:
print(msg)
await self.insert(msg,
None,
risotto_context,
'Info')
log = Logger()

View File

@ -3,15 +3,16 @@ from os.path import join, basename, dirname
from glob import glob
from tiramisu import StrOption, IntOption, BoolOption, ChoiceOption, OptionDescription, SymLinkOption, \
Config, Calculation, Params, ParamOption, ParamValue, calc_value, calc_value_property_help, \
Calculation, Params, ParamOption, ParamValue, calc_value, calc_value_property_help, \
groups, Option
from yaml import load, SafeLoader
from os import listdir
from os.path import isfile
from ..config import MESSAGE_ROOT_PATH
from ..config import get_config
from ..utils import _
MESSAGE_ROOT_PATH = get_config().get('global').get('message_root_path')
groups.addgroup('message')
@ -42,33 +43,25 @@ class MessageDefinition:
__slots__ = ('version',
'uri',
'description',
'sampleuse',
'domain',
'parameters',
'public',
'errors',
'pattern',
'related',
'response')
def __init__(self, raw_def):
def __init__(self, raw_def, message):
# default value for non mandatory key
self.version = u''
self.parameters = OrderedDict()
self.public = False
self.errors = []
self.related = []
self.response = None
self.sampleuse = None
# loads yaml information into object
for key, value in raw_def.items():
if isinstance(value, str):
value = value.strip()
if key == 'public':
if not isinstance(value, bool):
raise ValueError(_("{} must be a boolean, not {}").format(key, value))
elif key == 'pattern':
if key == 'pattern':
if value not in ['rpc', 'event', 'error']:
raise Exception(_('unknown pattern {}').format(value))
elif key == 'parameters':
@ -88,9 +81,8 @@ class MessageDefinition:
getattr(self, key)
except AttributeError:
raise Exception(_('mandatory key not set {} message').format(key))
# message with pattern = error must be public
if self.public is False and self.pattern == 'error':
raise Exception(_('Error message must be public : {}').format(self.uri))
if self.uri != message:
raise Exception(_(f'yaml file name "{message}.yml" does not match uri "{self.uri}"'))
class ParameterDefinition:
@ -217,8 +209,8 @@ def _parse_parameters(raw_defs):
return parameters
def parse_definition(filename: str):
return MessageDefinition(load(filename, Loader=SafeLoader))
def parse_definition(filecontent: bytes, message: str):
return MessageDefinition(load(filecontent, Loader=SafeLoader), message)
def is_message_defined(uri):
version, message = split_message_uri(uri)
@ -231,7 +223,7 @@ def get_message(uri):
version, message = split_message_uri(uri)
path = get_message_file_path(version, message)
with open(path, "r") as message_file:
message_content = parse_definition(message_file.read())
message_content = parse_definition(message_file.read(), message)
message_content.version = version
return message_content
except Exception as err:
@ -245,13 +237,16 @@ def split_message_uri(uri):
def get_message_file_path(version, message):
return join(MESSAGE_ROOT_PATH, version, 'messages', message + '.yml')
def list_messages():
def list_messages(uris):
messages = listdir(MESSAGE_ROOT_PATH)
messages.sort()
for version in messages:
for message in listdir(join(MESSAGE_ROOT_PATH, version, 'messages')):
if message.endswith('.yml'):
yield version + '.' + message.rsplit('.', 1)[0]
uri = version + '.' + message.rsplit('.', 1)[0]
if uris is not None and uri not in uris:
continue
yield uri
class CustomParam:
__slots__ = ('name',
@ -426,7 +421,7 @@ def _get_option(name,
'expected': ParamValue(optiondescription),
'reverse_condition': ParamValue(True)}),
calc_value_property_help))
description = arg.description.strip().rstrip()
kwargs = {'name': name,
'doc': _get_description(description, name),
@ -440,16 +435,19 @@ def _get_option(name,
kwargs['multi'] = True
type_ = type_[2:]
if type_ == 'Dict':
return DictOption(**kwargs)
obj = DictOption(**kwargs)
elif type_ == 'String':
return StrOption(**kwargs)
obj = StrOption(**kwargs)
elif type_ == 'Any':
return AnyOption(**kwargs)
obj = AnyOption(**kwargs)
elif 'Number' in type_ or type_ == 'ID' or type_ == 'Integer':
return IntOption(**kwargs)
obj = IntOption(**kwargs)
elif type_ == 'Boolean':
return BoolOption(**kwargs)
raise Exception('unsupported type {} in {}'.format(type_, file_path))
obj = BoolOption(**kwargs)
else:
raise Exception('unsupported type {} in {}'.format(type_, file_path))
obj.impl_set_information('ref', arg.ref)
return obj
def _parse_args(message_def,
@ -463,11 +461,15 @@ def _parse_args(message_def,
"""
new_options = OrderedDict()
for name, arg in message_def.parameters.items():
new_options[name] = arg
if arg.ref:
needs.setdefault(message_def.uri, {}).setdefault(arg.ref, []).append(name)
for name, arg in new_options.items():
current_opt = _get_option(name, arg, file_path, select_option, optiondescription)
#new_options[name] = arg
# if arg.ref:
# needs.setdefault(message_def.uri, {}).setdefault(arg.ref, []).append(name)
#for name, arg in new_options.items():
current_opt = _get_option(name,
arg,
file_path,
select_option,
optiondescription)
options.append(current_opt)
if hasattr(arg, 'shortarg') and arg.shortarg and load_shortarg:
options.append(SymLinkOption(arg.shortarg, current_opt))
@ -478,13 +480,7 @@ def _parse_responses(message_def,
"""build option with returns
"""
if message_def.response.parameters is None:
raise Exception('not implemented yet')
#name = 'response'
#keys['']['columns'][name] = {'description': message_def.response.description,
# 'type': message_def.response.type}
#responses = {}
#responses['keys'] = keys
#return responses
raise Exception('uri "{}" did not returned any valid parameters.'.format(message_def.uri))
options = []
names = []
@ -506,7 +502,7 @@ def _parse_responses(message_def,
# FIXME
'File': StrOption}.get(type_)
if not option:
raise Exception(f'unknown param type {obj.type}')
raise Exception(f'unknown param type {obj.type} in responses of message {message_def.uri}')
if hasattr(obj, 'default'):
kwargs['default'] = obj.default
else:
@ -580,19 +576,19 @@ def _get_root_option(select_option, optiondescriptions):
return OptionDescription('root', 'root', options_obj)
def get_messages(load_shortarg=False, only_public=False):
def get_messages(load_shortarg=False,
uris=None):
"""generate description from yml files
"""
optiondescriptions = OrderedDict()
optiondescriptions_name = []
optiondescriptions_info = {}
needs = OrderedDict()
messages = list(list_messages())
messages = list(list_messages(uris))
messages.sort()
for message_name in messages:
message_def = get_message(message_name)
if message_def.pattern not in ['rpc', 'event'] or \
(not message_def.public and only_public):
if message_def.pattern not in ['rpc', 'event']:
continue
optiondescriptions_name.append(message_def.uri)
optiondescriptions_name.sort()
@ -602,11 +598,9 @@ def get_messages(load_shortarg=False, only_public=False):
properties=frozenset(['mandatory', 'positional']))
for message_name in messages:
message_def = get_message(message_name)
if message_def.pattern not in ['rpc', 'event'] or \
(not message_def.public and only_public):
if message_def.pattern not in ['rpc', 'event']:
continue
optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern,
'public': message_def.public}
optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern}
if message_def.pattern == 'rpc':
optiondescriptions_info[message_def.uri]['response'] = _parse_responses(message_def,
message_name)
@ -622,12 +616,4 @@ def get_messages(load_shortarg=False, only_public=False):
load_shortarg)
root = _get_root_option(select_option, optiondescriptions)
try:
config = Config(root)
except Exception as err:
raise Exception('error when generating root optiondescription: {}'.format(err))
config.property.read_write()
# config.property.add('demoting_error_warning')
# return needs, responses, config
return optiondescriptions_info, root

View File

@ -1,15 +1,19 @@
from tiramisu import Config
from inspect import signature
from typing import Callable, Optional
from .utils import undefined, _
import asyncpg
from json import dumps, loads
from .utils import _
from .error import RegistrationError
from .message import get_messages
from .context import Context
from .config import INTERNAL_USER
from .config import INTERNAL_USER, get_config
from .logger import log
def register(uris: str,
notification: str=undefined):
notification: str=None):
""" Decorator to register function to the dispatcher
"""
if not isinstance(uris, list):
@ -29,6 +33,8 @@ class RegisterDispatcher:
def __init__(self):
# reference to instanciate module (to inject self in method): {"module_name": instance_of_module}
self.injected_self = {}
# postgresql pool
self.pool = None
# list of uris with informations: {"v1": {"module_name.xxxxx": yyyyyy}}
self.messages = {}
# load tiramisu objects
@ -45,32 +51,31 @@ class RegisterDispatcher:
first_argument_index = 1
return [param.name for param in list(signature(function).parameters.values())[first_argument_index:]]
def valid_rpc_params(self,
version: str,
message: str,
function: Callable,
module_name: str):
async def valid_rpc_params(self,
version: str,
message: str,
function: Callable,
module_name: str):
""" parameters function must have strictly all arguments with the correct name
"""
def get_message_args():
async def get_message_args():
# load config
config = Config(self.option)
config.property.read_write()
# set message to the uri name
config.option('message').value.set(message)
# get message argument
subconfig = config.option(message)
return set(config.option(message).value.dict().keys())
async with await Config(self.option) as config:
await config.property.read_write()
# set message to the uri name
await config.option('message').value.set(message)
# get message argument
dico = await config.option(message).value.dict()
return set(dico.keys())
def get_function_args():
function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it
if function_args and function_args[0] == 'risotto_context':
function_args = function_args[1:]
function_args = function_args[1:]
return set(function_args)
# get message arguments
message_args = get_message_args()
message_args = await get_message_args()
# get function arguments
function_args = get_function_args()
# compare message arguments with function parameter
@ -88,32 +93,31 @@ class RegisterDispatcher:
msg = _(' and ').join(msg)
raise RegistrationError(_(f'error with {module_name}.{function_name} arguments: {msg}'))
def valid_event_params(self,
version: str,
message: str,
function: Callable,
module_name: str):
async def valid_event_params(self,
version: str,
message: str,
function: Callable,
module_name: str):
""" parameters function validation for event messages
"""
def get_message_args():
async def get_message_args():
# load config
config = Config(self.option)
config.property.read_write()
# set message to the message name
config.option('message').value.set(message)
# get message argument
subconfig = config.option(message)
return set(config.option(message).value.dict().keys())
async with await Config(self.option) as config:
await config.property.read_write()
# set message to the message name
await config.option('message').value.set(message)
# get message argument
dico = await config.option(message).value.dict()
return set(dico.keys())
def get_function_args():
function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it
if function_args[0] == 'risotto_context':
function_args = function_args[1:]
function_args = function_args[1:]
return set(function_args)
# get message arguments
message_args = get_message_args()
message_args = await get_message_args()
# get function arguments
function_args = get_function_args()
# compare message arguments with function parameter
@ -146,29 +150,12 @@ class RegisterDispatcher:
# True if first argument is the risotto_context
function_args = self.get_function_args(function)
if function_args and function_args[0] == 'risotto_context':
inject_risotto_context = True
function_args.pop(0)
else:
inject_risotto_context = False
function_args.pop(0)
# check if already register
if 'function' in self.messages[version][message]:
raise RegistrationError(_(f'uri {version}.{message} already registered'))
# valid function's arguments
if self.messages[version][message]['pattern'] == 'rpc':
if notification is undefined:
function_name = function.__name__
raise RegistrationError(_(f'notification is mandatory when registered "{message}" with "{module_name}.{function_name}" even if you set None'))
valid_params = self.valid_rpc_params
else:
valid_params = self.valid_event_params
valid_params(version,
message,
function,
module_name)
# register
if self.messages[version][message]['pattern'] == 'rpc':
register = self.register_rpc
@ -179,7 +166,6 @@ class RegisterDispatcher:
module_name,
function,
function_args,
inject_risotto_context,
notification)
def register_rpc(self,
@ -188,12 +174,10 @@ class RegisterDispatcher:
module_name: str,
function: Callable,
function_args: list,
inject_risotto_context: bool,
notification: Optional[str]):
self.messages[version][message]['module'] = module_name
self.messages[version][message]['function'] = function
self.messages[version][message]['arguments'] = function_args
self.messages[version][message]['risotto_context'] = inject_risotto_context
if notification:
self.messages[version][message]['notification'] = notification
@ -203,24 +187,22 @@ class RegisterDispatcher:
module_name: str,
function: Callable,
function_args: list,
inject_risotto_context: bool,
notification: Optional[str]):
if 'functions' not in self.messages[version][message]:
self.messages[version][message]['functions'] = []
dico = {'module': module_name,
'function': function,
'arguments': function_args,
'risotto_context': inject_risotto_context}
if notification and notification is not undefined:
'arguments': function_args}
if notification and notification:
dico['notification'] = notification
self.messages[version][message]['functions'].append(dico)
def set_module(self, module_name, module):
def set_module(self, module_name, module, test):
""" register and instanciate a new module
"""
try:
self.injected_self[module_name] = module.Risotto()
self.injected_self[module_name] = module.Risotto(test)
except AttributeError as err:
raise RegistrationError(_(f'unable to register the module {module_name}, this module must have Risotto class'))
@ -231,14 +213,67 @@ class RegisterDispatcher:
for version, messages in self.messages.items():
for message, message_obj in messages.items():
if not 'functions' in message_obj and not 'function' in message_obj:
missing_messages.append(message)
if message_obj['pattern'] == 'event':
print(f'{message} prêche dans le désert')
else:
missing_messages.append(message)
if missing_messages:
raise RegistrationError(_(f'missing uri {missing_messages}'))
async def on_join(self):
for module_name, module in self.injected_self.items():
risotto_context = Context()
risotto_context.username = INTERNAL_USER
risotto_context.paths.append(f'{module_name}.on_join')
risotto_context.type = None
await module.on_join(risotto_context)
async with self.pool.acquire() as connection:
await connection.set_type_codec(
'json',
encoder=dumps,
decoder=loads,
schema='pg_catalog'
)
async with connection.transaction():
for module_name, module in self.injected_self.items():
risotto_context = Context()
risotto_context.username = INTERNAL_USER
risotto_context.paths.append(f'{module_name}.on_join')
risotto_context.type = None
risotto_context.connection = connection
info_msg = _(f'in module {module_name}.on_join')
await log.info_msg(risotto_context,
None,
info_msg)
await module.on_join(risotto_context)
async def insert_message(self,
connection,
uri):
sql = """INSERT INTO URI(URIName) VALUES ($1)
ON CONFLICT (URIName) DO NOTHING
"""
await connection.fetchval(sql,
uri)
async def load(self):
# valid function's arguments
db_conf = get_config()['database']['dsn']
self.pool = await asyncpg.create_pool(db_conf)
async with self.pool.acquire() as connection:
async with connection.transaction():
for version, messages in self.messages.items():
for message, message_infos in messages.items():
if message_infos['pattern'] == 'rpc':
module_name = message_infos['module']
function = message_infos['function']
await self.valid_rpc_params(version,
message,
function,
module_name)
else:
if 'functions' in message_infos:
for function_infos in message_infos['functions']:
module_name = function_infos['module']
function = function_infos['function']
await self.valid_event_params(version,
message,
function,
module_name)
await self.insert_message(connection,
f'{version}.{message}')

View File

@ -5,7 +5,8 @@ from ..dispatcher import dispatcher
def load_services(modules=None,
validate: bool=True):
validate: bool=True,
test: bool=False):
abs_here = dirname(abspath(__file__))
here = basename(abs_here)
module = basename(dirname(abs_here))
@ -14,6 +15,6 @@ def load_services(modules=None,
for filename in modules:
absfilename = join(abs_here, filename)
if isdir(absfilename) and isfile(join(absfilename, '__init__.py')):
dispatcher.set_module(filename, import_module(f'.{here}.{filename}', module))
dispatcher.set_module(filename, import_module(f'.{here}.{filename}', module), test)
if validate:
dispatcher.validate()

View File

@ -0,0 +1 @@
from .applicationservice import Risotto

View File

@ -0,0 +1,248 @@
from os import listdir
from os.path import join
from traceback import print_exc
from yaml import load, SafeLoader
from typing import Dict, List, Set
from ...controller import Controller
from ...register import register
from ...config import get_config
from ...error import ExecutionError
from ...context import Context
from ...utils import _
class Risotto(Controller):
def __init__(self,
test: bool) -> None:
self.source_root_path = get_config().get('source').get('root_path')
self.internal_source_name = get_config()['servermodel']['internal_source']
self.internal_distribution_name = get_config()['servermodel']['internal_distribution']
self.internal_release_name = get_config()['servermodel']['internal_release_name']
async def on_join(self,
risotto_context: Context) -> None:
internal_source = await self.call('v1.source.create',
risotto_context,
source_name=self.internal_source_name,
source_url='none')
internal_release = await self.call('v1.source.release.create',
risotto_context,
source_name=self.internal_source_name,
release_name=self.internal_release_name,
release_distribution=self.internal_distribution_name)
self.internal_release_id = internal_release['release_id']
async def _applicationservice_create(self,
risotto_context: Context,
applicationservice_name: str,
applicationservice_description: str,
applicationservice_dependencies: List[int],
release_id: int) -> Dict:
applicationservice_update_query = '''INSERT INTO Applicationservice(ApplicationserviceName, ApplicationserviceDescription, ApplicationserviceReleaseId)
VALUES ($1,$2,$3)
RETURNING ApplicationserviceId
'''
applicationservice_id = await risotto_context.connection.fetchval(applicationservice_update_query,
applicationservice_name,
applicationservice_description,
release_id)
await self.insert_dependency(risotto_context,
applicationservice_id,
applicationservice_dependencies)
return {'applicationservice_name': applicationservice_name,
'applicationservice_description': applicationservice_description,
'applicationservice_release_id': release_id,
'applicationservice_id': applicationservice_id}
async def insert_dependency(self,
risotto_context: Context,
applicationservice_id: int,
dependencies: list) -> None:
sql = '''INSERT INTO ApplicationserviceDependency(ApplicationserviceId, ApplicationserviceDependencyId)
VALUES ($1, $2)'''
for dependency in dependencies:
await risotto_context.connection.execute(sql,
applicationservice_id,
dependency)
@register('v1.applicationservice.dependency.add')
async def applicationservice_dependency_add(self,
risotto_context: Context,
applicationservice_name: str,
applicationservice_dependency: str,
source_name: str,
release_distribution: str) -> Dict:
release = await self.call('v1.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
as_descr = await self._applicationservice_describe(risotto_context,
applicationservice_name,
self.internal_release_id)
dependency_descr = await self._applicationservice_describe(risotto_context,
applicationservice_dependency,
release['release_id'])
sql = '''SELECT ApplicationserviceDependencyId
FROM ApplicationserviceDependency
WHERE ApplicationserviceId = $1 AND ApplicationserviceDependencyId = $2'''
if await risotto_context.connection.fetchrow(sql,
as_descr['applicationservice_id'],
dependency_descr['applicationservice_id']):
raise Exception(_(f'{applicationservice_name} has already a dependency to {applicationservice_dependency}'))
else:
await self.insert_dependency(risotto_context,
as_descr['applicationservice_id'],
[dependency_descr['applicationservice_id']])
await self.publish('v1.applicationservice.updated',
risotto_context,
**as_descr)
await self.updated_related_applicationservice(risotto_context,
as_descr['applicationservice_id'])
return as_descr
async def updated_related_applicationservice(self,
risotto_context: Context,
applicationservice_id: int) -> None:
sql = """
SELECT ApplicationserviceId as applicationservice_id
FROM ApplicationserviceDependency
WHERE ApplicationserviceDependencyId = $1"""
for dependency in await risotto_context.connection.fetch(sql,
applicationservice_id):
dependency_id = dependency['applicationservice_id']
applicationservice = await self._applicationservice_get_by_id(risotto_context,
dependency_id)
await self.publish('v1.applicationservice.updated',
risotto_context,
**applicationservice)
await self.updated_related_applicationservice(risotto_context,
dependency_id)
async def get_dependencies(self,
risotto_context: Context,
applicationservice_id: int) -> List[int]:
dependencies = set()
sql = """
SELECT ApplicationserviceDependencyId as applicationservice_dependency_id
FROM ApplicationserviceDependency
WHERE ApplicationserviceId = $1"""
for dependency in await risotto_context.connection.fetch(sql,
applicationservice_id):
dependencies.add(dependency['applicationservice_dependency_id'])
for dependency in dependencies.copy():
dependencies |= await self.get_dependencies(risotto_context,
dependency)
return dependencies
@register('v1.applicationservice.create')
async def applicationservice_create(self,
risotto_context: Context,
applicationservice_name: str,
applicationservice_description: str,
applicationservice_dependencies: List[int]) -> Dict:
applicationservice = await self._applicationservice_create(risotto_context,
applicationservice_name,
applicationservice_description,
applicationservice_dependencies,
self.internal_release_id)
dependencies = list(await self.get_dependencies(risotto_context,
applicationservice['applicationservice_id']))
applicationservice['applicationservice_dependencies'] = dependencies
return applicationservice
@register('v1.applicationservice.dataset.updated')
async def applicationservice_update(self,
risotto_context: Context,
source_name: str,
release_distribution: str) -> Dict:
release = await self.call('v1.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
applicationservice_path = join(self.source_root_path,
source_name,
release['release_name'],
'applicationservice')
release_id = release['release_id']
for service in listdir(applicationservice_path):
try:
applicationservice_description_path = join(applicationservice_path,
service,
'applicationservice.yml')
with open(applicationservice_description_path, 'r') as applicationservice_yml:
applicationservice_description = load(applicationservice_yml,
Loader=SafeLoader)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f'Error while reading {applicationservice_description_path}: {err}'))
try:
await self._applicationservice_create(risotto_context,
applicationservice_description['name'],
applicationservice_description['description'],
[],
release_id)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f"Error while injecting application service {applicationservice_description['name']} in database: {err}"))
return {'retcode': 0,
'returns': _('Application Services successfully loaded')}
@register('v1.applicationservice.get_by_id')
async def applicationservice_get_by_id(self,
risotto_context: Context,
applicationservice_id: int) -> Dict:
return await self._applicationservice_get_by_id(risotto_context,
applicationservice_id)
async def _applicationservice_get_by_id(self,
risotto_context: Context,
applicationservice_id: int) -> Dict:
applicationservice_query = """
SELECT ApplicationserviceId as applicationservice_id, ApplicationserviceName as applicationservice_name, ApplicationserviceReleaseId as applicationservice_release_id
FROM applicationservice
WHERE ApplicationserviceId=$1"""
applicationservice = await risotto_context.connection.fetchrow(applicationservice_query,
applicationservice_id)
if applicationservice is None:
raise Exception(_(f'unknown service with ID {applicationservice_id}'))
dependencies = list(await self.get_dependencies(risotto_context,
applicationservice['applicationservice_id']))
applicationservice = dict(applicationservice)
applicationservice['applicationservice_dependencies'] = dependencies
return applicationservice
async def _applicationservice_describe(self,
risotto_context: Context,
applicationservice_name,
release_id):
applicationservice_query = """
SELECT ApplicationserviceId as applicationservice_id, ApplicationserviceName as applicationservice_name, ApplicationserviceReleaseId as applicationservice_release_id
FROM Applicationservice
WHERE ApplicationserviceName=$1 AND ApplicationserviceReleaseId=$2"""
applicationservice = await risotto_context.connection.fetchrow(applicationservice_query,
applicationservice_name,
release_id)
if applicationservice is None:
raise Exception(_(f'unknown service {applicationservice_name} in release ID {release_id}'))
return dict(applicationservice)
@register('v1.applicationservice.describe')
async def applicationservice_describe(self,
risotto_context: Context,
applicationservice_name,
source_name,
release_distribution):
release = await self.call('v1.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
applicationservice = await self._applicationservice_describe(risotto_context,
applicationservice_name,
release['release_id'])
dependencies = list(await self.get_dependencies(risotto_context,
applicationservice['applicationservice_id']))
applicationservice['applicationservice_dependencies'] = dependencies
return applicationservice

View File

@ -7,10 +7,11 @@ from typing import Dict, List
from tiramisu import Storage, delete_session, MetaConfig, MixConfig
from rougail import load as rougail_load
from rougail.config import dtdfilename
from ...controller import Controller
from ...register import register
from ...config import ROOT_CACHE_DIR, DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH
from ...config import get_config
from ...context import Context
from ...utils import _
from ...error import CallError, RegistrationError
@ -18,15 +19,20 @@ from ...logger import log
class Risotto(Controller):
servermodel = {}
server = {}
def __init__(self) -> None:
for dirname in [ROOT_CACHE_DIR, DATABASE_DIR]:
if not isdir(dirname):
raise RegistrationError(_(f'unable to find the cache dir "{dirname}"'))
self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
super().__init__()
def __init__(self,
test) -> None:
global conf_storage
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
if not isdir(self.cache_root_path):
raise RegistrationError(_(f'unable to find the cache dir "{self.cache_root_path}"'))
if not test:
db_conf = get_config()['database']['tiramisu_dsn']
self.save_storage = Storage(engine='postgres')
self.save_storage.setting(dsn=db_conf)
self.servermodel = {}
self.server = {}
super().__init__(test)
async def on_join(self,
risotto_context: Context) -> None:
@ -39,9 +45,9 @@ class Risotto(Controller):
risotto_context: Context) -> None:
""" load all available servermodels
"""
log.info_msg(risotto_context,
None,
'Load servermodels')
await log.info_msg(risotto_context,
None,
'Load servermodels')
servermodels = await self.call('v1.servermodel.list',
risotto_context)
@ -49,238 +55,218 @@ class Risotto(Controller):
for servermodel in servermodels:
try:
await self.load_servermodel(risotto_context,
servermodel['servermodelid'],
servermodel['servermodelname'])
servermodel['servermodel_id'],
servermodel['servermodel_name'])
except CallError as err:
pass
# do link to this servermodel
for servermodel in servermodels:
if 'servermodelparentsid' in servermodel:
for servermodelparentid in servermodel['servermodelparentsid']:
self.servermodel_legacy(risotto_context,
servermodel['servermodelname'],
servermodel['servermodelid'],
servermodelparentid)
if 'servermodel_parents_id' in servermodel:
for servermodelparentid in servermodel['servermodel_parents_id']:
await self.servermodel_legacy(risotto_context,
servermodel['servermodel_name'],
servermodel['servermodel_id'],
servermodelparentid)
def get_funcs_filename(self,
servermodelid: int):
return join(ROOT_CACHE_DIR, str(servermodelid)+".creolefuncs")
servermodel_id: int):
return join(self.cache_root_path, str(servermodel_id), "funcs.py")
async def load_servermodel(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str) -> None:
servermodel_id: int,
servermodel_name: str) -> None:
""" Loads a servermodel
"""
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
funcs_file = self.get_funcs_filename(servermodelid)
log.info_msg(risotto_context,
None,
f'Load servermodel {servermodelname} ({servermodelid})')
cache_file = join(self.cache_root_path, str(servermodel_id), "dictionaries.xml")
funcs_file = self.get_funcs_filename(servermodel_id)
await log.info_msg(risotto_context,
None,
f'Load servermodel {servermodel_name} ({servermodel_id})')
# use file in cache if found, otherwise retrieve it in servermodel context
if isfile(cache_file):
fileio = open(cache_file)
else:
servermodel = await self.call('v1.servermodel.describe',
risotto_context,
servermodelid=servermodelid,
inheritance=False,
resolvdepends=False,
schema=True,
creolefuncs=True)
fileio = BytesIO()
fileio.write(servermodel['schema'].encode())
fileio.seek(0)
# use file in cache
with open(cache_file) as fileio:
xmlroot = parse(fileio).getroot()
try:
self.servermodel[servermodel_id] = await self.build_metaconfig(servermodel_id,
servermodel_name,
xmlroot,
funcs_file)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
msg = _(f'unable to load {servermodel_name}: {err}')
await log.error_msg(risotto_context,
None,
msg)
with open(cache_file, 'w') as cache:
cache.write(servermodel['schema'])
with open(funcs_file, 'w') as cache:
cache.write(servermodel['creolefuncs'])
del servermodel
# loads tiramisu config and store it
xmlroot = parse(fileio).getroot()
self.servermodel[servermodelid] = self.build_metaconfig(servermodelid,
servermodelname,
xmlroot,
funcs_file)
def build_metaconfig(self,
servermodelid: int,
servermodelname: str,
xmlroot: str,
funcs_file: str) -> MetaConfig:
async def build_metaconfig(self,
servermodel_id: int,
servermodel_name: str,
xmlroot: str,
funcs_file: str) -> MetaConfig:
""" Build metaconfig for a servermodel
"""
# build tiramisu's session ID
session_id = f'v_{servermodelid}'
session_id = f'v_{servermodel_id}'
optiondescription = rougail_load(xmlroot,
ROUGAIL_DTD_PATH,
dtdfilename,
funcs_file)
# build servermodel metaconfig (v_xxx.m_v_xxx)
metaconfig = MetaConfig([],
optiondescription=optiondescription,
persistent=True,
session_id=session_id,
storage=self.save_storage)
mixconfig = MixConfig(children=[],
optiondescription=optiondescription,
persistent=True,
session_id='m_' + session_id,
storage=self.save_storage)
metaconfig.config.add(mixconfig)
metaconfig = await MetaConfig([],
optiondescription=optiondescription,
session_id=session_id,
storage=self.save_storage)
mixconfig = await MixConfig(children=[],
optiondescription=optiondescription,
session_id='m_' + session_id,
storage=self.save_storage)
await metaconfig.config.add(mixconfig)
# change default rights
ro_origin = metaconfig.property.getdefault('read_only', 'append')
ro_origin = await metaconfig.property.getdefault('read_only', 'append')
ro_append = frozenset(ro_origin - {'force_store_value'})
rw_origin = metaconfig.property.getdefault('read_write', 'append')
rw_origin = await metaconfig.property.getdefault('read_write', 'append')
rw_append = frozenset(rw_origin - {'force_store_value'})
metaconfig.property.setdefault(ro_append, 'read_only', 'append')
metaconfig.property.setdefault(rw_append, 'read_write', 'append')
await metaconfig.property.setdefault(ro_append, 'read_only', 'append')
await metaconfig.property.setdefault(rw_append, 'read_write', 'append')
metaconfig.property.read_only()
metaconfig.permissive.add('basic')
metaconfig.permissive.add('normal')
metaconfig.permissive.add('expert')
await metaconfig.property.read_only()
await metaconfig.permissive.add('basic')
await metaconfig.permissive.add('normal')
await metaconfig.permissive.add('expert')
# set informtion and owner
metaconfig.owner.set('v_{}'.format(servermodelname))
metaconfig.information.set('servermodel_id', servermodelid)
metaconfig.information.set('servermodel_name', servermodelname)
await metaconfig.owner.set('v_{}'.format(servermodel_name))
await metaconfig.information.set('servermodel_id', servermodel_id)
await metaconfig.information.set('servermodel_name', servermodel_name)
# return configuration
return metaconfig
def servermodel_legacy(self,
risotto_context: Context,
servermodel_name: str,
servermodel_id: int,
servermodel_parent_id: int) -> None:
async def servermodel_legacy(self,
risotto_context: Context,
servermodel_name: str,
servermodel_id: int,
servermodel_parent_id: int) -> None:
""" Make link between parent and children
"""
if servermodel_parent_id is None:
return
if not self.servermodel.get(servermodel_parent_id):
if DEBUG:
msg = _(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})')
log.error_msg(risotto_context,
None,
msg)
msg = _(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})')
await log.error_msg(risotto_context,
None,
msg)
return
servermodel_parent = self.servermodel[servermodel_parent_id]
servermodel_parent_name = servermodel_parent.information.get('servermodel_name')
if DEBUG:
msg = _(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})')
log.info_msg(risotto_context,
None,
msg)
servermodel_parent_name = await servermodel_parent.information.get('servermodel_name')
msg = _(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})')
await log.info_msg(risotto_context,
None,
msg)
# do link
mix = servermodel_parent.config.get('m_v_' + str(servermodel_parent_id))
mix = await servermodel_parent.config.get('m_v_' + str(servermodel_parent_id))
try:
mix.config.add(self.servermodel[servermodel_id])
await mix.config.add(self.servermodel[servermodel_id])
except Exception as err:
if DEBUG:
log.error_msg(risotto_context,
None,
str(err))
await log.error_msg(risotto_context,
None,
str(err))
async def load_servers(self,
risotto_context: Context) -> None:
""" load all available servers
"""
log.info_msg(risotto_context,
None,
f'Load servers')
await log.info_msg(risotto_context,
None,
f'Load servers')
# get all servers
servers = await self.call('v1.server.list',
risotto_context)
# loads servers
for server in servers:
try:
self.load_server(risotto_context,
server['server_id'],
server['servername'],
server['servermodelid'])
await self.load_server(risotto_context,
server['server_id'],
server['server_name'],
server['server_servermodel_id'])
except Exception as err:
if DEBUG:
if get_config().get('global').get('debug'):
print_exc()
servername = server['servername']
server_name = server['server_name']
server_id = server['server_id']
msg = _(f'unable to load server {servername} ({server_id}): {err}')
log.error_msg(risotto_context,
None,
msg)
msg = _(f'unable to load server {server_name} ({server_id}): {err}')
await log.error_msg(risotto_context,
None,
msg)
def load_server(self,
risotto_context: Context,
server_id: int,
servername: str,
servermodelid: int) -> None:
async def load_server(self,
risotto_context: Context,
server_id: int,
server_name: str,
server_servermodel_id: int) -> None:
""" Loads a server
"""
if server_id in self.server:
return
log.info_msg(risotto_context,
None,
f'Load server {servername} ({server_id})')
if not servermodelid in self.servermodel:
msg = f'unable to find servermodel with id {servermodelid}'
log.error_msg(risotto_context,
None,
msg)
await log.info_msg(risotto_context,
None,
f'Load server {server_name} ({server_id})')
if not server_servermodel_id in self.servermodel:
msg = f'unable to find servermodel with id {server_servermodel_id}'
await log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
# check if server was already created
session_id = f's_{server_id}'
# get the servermodel's metaconfig
metaconfig = self.servermodel[servermodelid]
metaconfig = self.servermodel[server_servermodel_id]
# create server configuration and server 'to deploy' configuration and store it
self.server[server_id] = {'server': self.build_config(session_id,
server_id,
servername,
metaconfig),
'server_to_deploy': self.build_config(f'std_{server_id}',
server_id,
servername,
metaconfig),
'funcs_file': self.get_funcs_filename(servermodelid)}
self.server[server_id] = {'server': await self.build_config(session_id,
server_id,
server_name,
metaconfig),
'server_to_deploy': await self.build_config(f'std_{server_id}',
server_id,
server_name,
metaconfig),
'funcs_file': self.get_funcs_filename(server_servermodel_id)}
def build_config(self,
session_id: str,
server_id: int,
servername: str,
metaconfig: MetaConfig) -> None:
async def build_config(self,
session_id: str,
server_id: int,
server_name: str,
metaconfig: MetaConfig) -> None:
""" build server's config
"""
config = metaconfig.config.new(session_id,
storage=self.save_storage,
persistent=True)
config.information.set('server_id', server_id)
config.information.set('server_name', servername)
config.owner.set(servername)
config.property.read_only()
config = await metaconfig.config.new(session_id,
storage=self.save_storage)
await config.information.set('server_id', server_id)
await config.information.set('server_name', server_name)
await config.owner.set(server_name)
await config.property.read_only()
return config
@register('v1.server.created')
async def server_created(self,
risotto_context: Context,
server_id: int,
servername: str,
servermodelid: int) -> None:
server_name: str,
server_servermodel_id: int) -> None:
""" Loads server's configuration when a new server is created
"""
self.load_server(risotto_context,
server_id,
servername,
servermodelid)
await self.load_server(risotto_context,
server_id,
server_name,
server_servermodel_id)
@register('v1.server.deleted')
async def server_deleted(self,
@ -288,109 +274,114 @@ class Risotto(Controller):
# delete config to it's parents
for server_type in ['server', 'server_to_deploy']:
config = self.server[server_id]['server']
for parent in config.config.parents():
parent.config.pop(config.config.name())
for parent in await config.config.parents():
await parent.config.pop(await config.config.name())
delete_session(storage=self.save_storage,
session_id=config.config.name())
session_id=await config.config.name())
# delete metaconfig
del self.server[server_id]
@register('v1.servermodel.created')
async def servermodel_created(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str,
servermodelparentsid: List[int]) -> None:
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
""" when servermodels are created, load it and do link
"""
await self.load_and_link_servermodel(risotto_context,
servermodelid,
servermodelname,
servermodelparentsid)
servermodel_id,
servermodel_name,
servermodel_parents_id)
async def load_and_link_servermodel(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str,
servermodelparentsid: List[int]) -> None:
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
await self.load_servermodel(risotto_context,
servermodelid,
servermodelname)
if servermodelparentsid is not None:
for servermodelparentid in servermodelparentsid:
self.servermodel_legacy(risotto_context,
servermodelname,
servermodelid,
servermodelparentid)
servermodel_id,
servermodel_name)
if servermodel_parents_id is not None:
for servermodelparentid in servermodel_parents_id:
await self.servermodel_legacy(risotto_context,
servermodel_name,
servermodel_id,
servermodelparentid)
def servermodel_delete(self,
servermodelid: int) -> List[MetaConfig]:
metaconfig = self.servermodel.pop(servermodelid)
mixconfig = next(metaconfig.config.list())
async def servermodel_delete(self,
servermodel_id: int) -> List[MetaConfig]:
metaconfig = self.servermodel.pop(servermodel_id)
mixconfig = await metaconfig.config.list()[0]
children = []
for child in mixconfig.config.list():
for child in await mixconfig.config.list():
children.append(child)
mixconfig.config.pop(child.config.name())
metaconfig.config.pop(mixconfig.config.name())
await mixconfig.config.pop(await child.config.name())
await metaconfig.config.pop(await mixconfig.config.name())
delete_session(storage=self.save_storage,
session_id=mixconfig.config.name())
session_id=await mixconfig.config.name())
del mixconfig
for parent in metaconfig.config.parents():
parent.config.pop(metaconfig.config.name())
for parent in await metaconfig.config.parents():
await parent.config.pop(await metaconfig.config.name())
delete_session(storage=self.save_storage,
session_id=metaconfig.config.name())
session_id=await metaconfig.config.name())
return children
#
# @register('v1.servermodel.updated')
# async def servermodel_updated(self,
# risotto_context: Context,
# servermodel_id: int,
# servermodel_name: str,
# servermodel_parents_id: List[int]) -> None:
# log.info_msg(risotto_context,
# None,
# f'Reload servermodel {servermodel_name} ({servermodel_id})')
# # unlink cache to force download new aggregated file
# cache_file = join(self.cache_root_path, str(servermodel_id)+".xml")
# if isfile(cache_file):
# unlink(cache_file)
#
# # store all informations
# if servermodel_id in self.servermodel:
# old_values = await self.servermodel[servermodel_id].value.exportation()
# old_permissives = await self.servermodel[servermodel_id].permissive.exportation()
# old_properties = await self.servermodel[servermodel_id].property.exportation()
# children = await self.servermodel_delete(servermodel_id)
# else:
# old_values = None
#
# # create new one
# await self.load_and_link_servermodel(risotto_context,
# servermodel_id,
# servermodel_name,
# servermodel_parents_id)
#
# # migrates informations
# if old_values is not None:
# await self.servermodel[servermodel_id].value.importation(old_values)
# await self.servermodel[servermodel_id].permissive.importation(old_permissives)
# await self.servermodel[servermodel_id].property.importation(old_properties)
# for child in children:
# await self.servermodel_legacy(risotto_context,
# await child.information.get('servermodel_name'),
# await child.information.get('servermodel_id'),
# servermodel_id)
@register('v1.servermodel.updated')
async def servermodel_updated(self,
risotto_context: Context,
servermodelid: int,
servermodelname: str,
servermodelparentsid: List[int]) -> None:
log.info_msg(risotto_context,
None,
f'Reload servermodel {servermodelname} ({servermodelid})')
# unlink cache to force download new aggregated file
cache_file = join(ROOT_CACHE_DIR, str(servermodelid)+".xml")
if isfile(cache_file):
unlink(cache_file)
# store all informations
if servermodelid in self.servermodel:
old_values = self.servermodel[servermodelid].value.exportation()
old_permissives = self.servermodel[servermodelid].permissive.exportation()
old_properties = self.servermodel[servermodelid].property.exportation()
children = self.servermodel_delete(servermodelid)
else:
old_values = None
# create new one
await self.load_and_link_servermodel(risotto_context,
servermodelid,
servermodelname,
servermodelparentsid)
# migrates informations
if old_values is not None:
self.servermodel[servermodelid].value.importation(old_values)
self.servermodel[servermodelid].permissive.importation(old_permissives)
self.servermodel[servermodelid].property.importation(old_properties)
for child in children:
self.servermodel_legacy(risotto_context,
child.information.get('servermodel_name'),
child.information.get('servermodel_id'),
servermodelid)
@register('v1.config.configuration.server.get', None)
@register('v1.config.configuration.server.get')
async def get_configuration(self,
server_id: int,
risotto_context: Context,
server_name: str,
deployed: bool) -> bytes:
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
if server_id not in self.server:
msg = _(f'cannot find server with id {server_id}')
log.error_msg(risotto_context,
None,
msg)
await log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
if deployed:
@ -398,44 +389,52 @@ class Risotto(Controller):
else:
server = self.server[server_id]['server_to_deploy']
server.property.read_only()
await server.property.read_only()
try:
configuration = server.value.dict(fullpath=True)
configuration = await server.value.dict(fullpath=True,
leader_to_list=True)
except:
if deployed:
msg = _(f'No configuration available for server {server_id}')
else:
msg = _(f'No undeployed configuration available for server {server_id}')
log.error_msg(risotto_context,
None,
msg)
await log.error_msg(risotto_context,
None,
msg)
raise CallError(msg)
return {'server_id': server_id,
return {'server_name': server_name,
'deployed': deployed,
'configuration': configuration}
@register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated')
async def deploy_configuration(self,
server_id: int) -> Dict:
risotto_context: Context,
server_name: str) -> Dict:
"""Copy values, permissions, permissives from config 'to deploy' to active config
"""
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
# FIXME is server_to_deploy working?
config = self.server[server_id]['server']
config_std = self.server[server_id]['server_to_deploy']
# when deploy, calculate force_store_value
ro = config_std.property.getdefault('read_only', 'append')
ro = await config_std.property.getdefault('read_only', 'append')
if 'force_store_value' not in ro:
ro = frozenset(list(ro) + ['force_store_value'])
config_std.property.setdefault(ro, 'read_only', 'append')
rw = config_std.property.getdefault('read_write', 'append')
await config_std.property.setdefault(ro, 'read_only', 'append')
rw = await config_std.property.getdefault('read_write', 'append')
rw = frozenset(list(rw) + ['force_store_value'])
config_std.property.setdefault(rw, 'read_write', 'append')
config_std.property.add('force_store_value')
await config_std.property.setdefault(rw, 'read_write', 'append')
await config_std.property.add('force_store_value')
# copy informations from server 'to deploy' configuration to server configuration
config.value.importation(config_std.value.exportation())
config.permissive.importation(config_std.permissive.exportation())
config.property.importation(config_std.property.exportation())
await config.value.importation(await config_std.value.exportation())
await config.permissive.importation(await config_std.permissive.exportation())
await config.property.importation(await config_std.property.exportation())
return {'server_id': server_id,
'server_name': server_name,
'deployed': True}

View File

@ -1,8 +1,69 @@
from typing import Dict
from ...controller import Controller
from ...register import register
from ...context import Context
from ...config import get_config
from ...utils import _
class Risotto(Controller):
@register('v1.server.list', None)
async def server_list(self):
return [{'server_id': 1, 'servername': 'one', 'serverdescription': 'the first', 'servermodelid': 1}]
def __init__(self,
test: bool) -> None:
self.internal_source_name = get_config()['servermodel']['internal_source']
@register('v1.server.list')
async def server_list(self,
risotto_context: Context) -> Dict:
sql = '''
SELECT ServerId as server_id, ServerName as server_name, ServerDescription as server_description, ServerServermodelId as server_servermodel_id
FROM Server
'''
servers = await risotto_context.connection.fetch(sql)
return [dict(r) for r in servers]
@register('v1.server.create', 'v1.server.created')
async def server_create(self,
risotto_context: Context,
server_name: str,
server_description: str,
servermodel_name: str,
release_distribution: str) -> Dict:
servermodel = await self.call('v1.servermodel.describe',
risotto_context,
servermodel_name=servermodel_name,
source_name=self.internal_source_name,
release_distribution=release_distribution)
server_insert = """INSERT INTO Server(ServerName, ServerDescription, ServerServermodelId)
VALUES ($1,$2,$3)
RETURNING ServerId
"""
server_id = await risotto_context.connection.fetchval(server_insert,
server_name,
server_description,
servermodel['servermodel_id'])
await self.call('v1.user.role.create',
risotto_context,
user_login=risotto_context.username,
role_name='server_rw',
role_attribute='Server.ServerName',
role_attribute_value=server_name)
return {'server_id': server_id,
'server_name': server_name,
'server_description': server_description,
'server_servermodel_id': servermodel['servermodel_id']}
@register('v1.server.describe')
async def server_describe(self,
risotto_context: Context,
server_name: str) -> Dict:
sql = '''
SELECT ServerId as server_id, ServerName as server_name, ServerDescription as server_description, ServerServermodelId as server_servermodel_id
FROM Server
WHERE ServerName = $1
'''
server = await risotto_context.connection.fetchrow(sql,
server_name)
if not server:
raise Exception(_(f'unable to find server with name {server_name}'))
return dict(server)

View File

@ -0,0 +1,172 @@
from os.path import join, isdir, isfile
from os import listdir, makedirs
from shutil import rmtree, copyfile
from typing import Dict, List, Optional
from rougail import CreoleObjSpace
from rougail.config import dtdfilename
from ...controller import Controller
from ...context import Context
from ...logger import log
from ...utils import _
class Generator(Controller):
async def generate(self,
risotto_context: Context,
servermodel_name: str,
servermodel_id: int,
dependencies: List[int],
generate_cache: Optional[Dict]=None) -> None:
if generate_cache is None:
generate_cache = {'applicationservice': {},
'release_id': {}}
await self.servermodel_gen_funcs(servermodel_name,
servermodel_id,
dependencies,
generate_cache,
risotto_context)
await self.servermodel_gen_schema(servermodel_name,
servermodel_id,
dependencies,
generate_cache,
risotto_context)
await self.servermodel_copy_templates(servermodel_name,
servermodel_id,
dependencies,
generate_cache,
risotto_context)
async def servermodel_gen_funcs(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
generate_cache: Dict,
risotto_context: Context) -> None:
as_names = []
dest_file = self.get_servermodel_cache(servermodel_id, 'funcs.py')
with open(dest_file, 'wb') as funcs:
funcs.write(b'from tiramisu import valid_network_netmask, valid_ip_netmask, valid_broadcast, valid_in_network, valid_not_equal as valid_differ, valid_not_equal, calc_value\n\n')
for dependency in dependencies:
if dependency not in generate_cache['applicationservice']:
applicationservice = await self.call('v1.applicationservice.get_by_id',
risotto_context,
applicationservice_id=dependency)
generate_cache['applicationservice'][dependency] = (applicationservice['applicationservice_name'],
applicationservice['applicationservice_release_id'])
applicationservice_name, release_id = generate_cache['applicationservice'][dependency]
if release_id not in generate_cache['release_id']:
release = await self.call('v1.source.release.get_by_id',
risotto_context,
release_id=release_id)
generate_cache['release_id'][release_id] = (release['source_name'],
release['release_name'])
source_name, release_name = generate_cache['release_id'][release_id]
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'funcs')
if isdir(path):
as_names.append(applicationservice_name)
for fil in listdir(path):
if not fil.endswith('.py'):
continue
fil_path = join(path, fil)
with open(fil_path, 'rb') as fh:
funcs.write(f'# {fil_path}\n'.encode())
funcs.write(fh.read())
funcs.write(b'\n')
as_names_str = '", "'.join(as_names)
await log.info(risotto_context,
_(f'gen funcs for "{servermodel_name}" with application services "{as_names_str}"'))
async def servermodel_gen_schema(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
generate_cache: Dict,
risotto_context: Context) -> None:
paths = []
extras = []
as_names = set()
for dependency in dependencies:
applicationservice_name, release_id = generate_cache['applicationservice'][dependency]
source_name, release_name = generate_cache['release_id'][release_id]
# load creole dictionaries
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'dictionaries')
if isdir(path):
as_names.add(applicationservice_name)
paths.append(path)
# load extra dictionaries
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'extras')
if isdir(path):
for namespace in listdir(path):
extra_dir = join(path, namespace)
if not isdir(extra_dir):
continue
as_names.add(applicationservice_name)
extras.append((namespace, [extra_dir]))
eolobj = CreoleObjSpace(dtdfilename)
as_names_str = '", "'.join(as_names)
await log.info(risotto_context,
_(f'gen schema for "{servermodel_name}" with application services "{as_names_str}"'))
eolobj.create_or_populate_from_xml('creole', paths)
for extra in extras:
eolobj.create_or_populate_from_xml(extra[0], extra[1])
# FIXME extra
funcs_file = self.get_servermodel_cache(servermodel_id, 'funcs.py')
eolobj.space_visitor(funcs_file)
dest_dir = self.get_servermodel_cache(servermodel_id, 'dictionaries.xml')
eolobj.save(dest_dir)
def get_servermodel_cache(self,
servermodel_id: int,
subdir: Optional[str]=None) -> str:
if subdir:
return join(self.cache_root_path, str(servermodel_id), subdir)
return join(self.cache_root_path, str(servermodel_id))
async def servermodel_copy_templates(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
generate_cache: Dict,
risotto_context: Context) -> None:
as_names = []
dest_dir = self.get_servermodel_cache(servermodel_id, 'templates')
if isdir(dest_dir):
rmtree(dest_dir)
makedirs(dest_dir)
for dependency in dependencies:
applicationservice_name, release_id = generate_cache['applicationservice'][dependency]
source_name, release_name = generate_cache['release_id'][release_id]
path = join(self.source_root_path,
source_name,
release_name,
'applicationservice',
applicationservice_name,
'templates')
if isdir(path):
for template in listdir(path):
template_path = join(dest_dir, template)
if isfile(template_path):
as_names_str = '", "'.join(as_names)
raise Exception(_(f'duplicate "{template}" when copying template from "{applicationservice_name}" to "{dest_dir}" for servermodel "{servermodel_name}" (previous application services was "{as_names_str}"'))
copyfile(join(path, template), template_path)
as_names.append(applicationservice_name)
as_names_str = '", "'.join(as_names)
await log.info(risotto_context,
_(f'copy templates for "{servermodel_name}" with application services "{as_names_str}"'))

View File

@ -1,65 +1,282 @@
from ...controller import Controller
from shutil import rmtree
from os import listdir, makedirs
from os.path import join, isdir
from yaml import load, SafeLoader
from traceback import print_exc
from typing import Dict, List, Optional
from .generator import Generator
from ...register import register
from ...utils import _
from ...context import Context
from ...config import get_config
from ...error import ExecutionError
from ...logger import log
class Risotto(Controller):
@register('v1.servermodel.list', None)
async def servermodel_list(self, sourceid):
return [{'servermodelid': 1,
'servermodelname': 'name',
'subreleasename': 'name',
'sourceid': 1,
'servermodeldescription': 'description'}]
@register('v1.servermodel.describe', None)
async def servermodel_describe(self, inheritance, creolefuncs, servermodelid, schema, conffiles, resolvdepends, probes):
schema = """<?xml version='1.0' encoding='UTF-8'?>
<creole>
<family name="containers">
<family name="container0" doc="test">
<family doc="files" name="files">
<family doc="file0" name="file0">
<variable doc="" multi="False" name="mkdir" type="boolean">
<value>False</value>
</variable>
<variable doc="" multi="False" name="name" type="string">
<value>/etc/mailname</value>
</variable>
<variable doc="" multi="False" name="rm" type="boolean">
<value>False</value>
</variable>
<variable doc="" multi="False" name="source" type="string">
<value>mailname</value>
</variable>
<variable doc="" multi="False" name="activate" type="boolean">
<value>True</value>
</variable>
</family>
</family>
<property>basic</property>
</family>
</family>
<family doc="" name="creole">
<family doc="general" name="general">
<property>normal</property>
<variable doc="No change" multi="False" name="mode_conteneur_actif" type="choice">
<choice type="string">oui</choice>
<choice type="string">non</choice>
<property>mandatory</property>
<property>normal</property>
<value type="string">non</value>
</variable>
<leader doc="master" name="master">
<property>normal</property>
<variable doc="master" multi="True" name="master" type="string"/>
<variable doc="slave1" multi="True" name="slave1" type="string">
<property>normal</property>
</variable>
<variable doc="slave2" multi="True" name="slave2" type="string">
<property>normal</property>
</variable>
</leader>
</family>
<separators/>
</family>
</creole>"""
return {'servermodelid': 1, 'servermodelname': 'name', 'servermodeldescription': 'description', 'subreleasename': 'name', 'sourceid': 1, 'schema': schema, 'creolefuncs': ''}
class Risotto(Generator):
def __init__(self,
test: bool) -> None:
self.source_root_path = get_config()['source']['root_path']
self.cache_root_path = join(get_config()['cache']['root_path'], 'servermodel')
self.internal_source_name = get_config()['servermodel']['internal_source']
self.internal_distribution_name = get_config()['servermodel']['internal_distribution']
self.internal_release_name = get_config()['servermodel']['internal_release_name']
if not isdir(self.cache_root_path):
makedirs(join(self.cache_root_path))
async def on_join(self,
risotto_context: Context) -> None:
internal_release = await self.call('v1.source.release.describe',
risotto_context,
source_name=self.internal_source_name,
release_distribution=self.internal_distribution_name)
self.internal_release_id = internal_release['release_id']
async def _servermodel_create(self,
risotto_context: Context,
servermodel_name: str,
servermodel_description: str,
servermodel_parents: List[Dict],
dependencies: List[int],
release_id: int,
generate_cache: Dict=None) -> Dict:
if generate_cache is None:
generate_cache = {'applicationservice': {},
'release_id': {}}
servermodel_insert = """INSERT INTO Servermodel(ServermodelName, ServermodelDescription, ServermodelParentsId, ServermodelReleaseId, ServermodelApplicationServiceId)
VALUES ($1,$2,$3,$4,$5)
RETURNING ServermodelId
"""
as_name = f"local_{servermodel_name}"
as_description = f'local application service for {servermodel_name}'
servermodel_parents_id = []
for servermodel_parent in servermodel_parents:
servermodel_parents_id.append(servermodel_parent['servermodel_id'])
dependencies.append(servermodel_parent['servermodel_applicationservice_id'])
applicationservice = await self.call('v1.applicationservice.create',
risotto_context,
applicationservice_name=as_name,
applicationservice_description=as_description,
applicationservice_dependencies=dependencies)
applicationservice_id = applicationservice['applicationservice_id']
generate_cache['applicationservice'][applicationservice_id] = (as_name,
self.internal_release_id)
if self.internal_release_id not in generate_cache['release_id']:
generate_cache['release_id'][self.internal_release_id] = (self.internal_source_name,
self.internal_release_name)
servermodel_id = await risotto_context.connection.fetchval(servermodel_insert,
servermodel_name,
servermodel_description,
servermodel_parents_id,
release_id,
applicationservice_id)
dest_dir = self.get_servermodel_cache(servermodel_id)
if isdir(dest_dir):
rmtree(dest_dir)
makedirs(dest_dir)
dependencies = applicationservice['applicationservice_dependencies']
# for as_release_id in dependencies.values():
# applicationservice_name, as_release_id = applicationservice_infos
# if as_release_id not in release_cache:
# release_cache[as_release_id] = await self.call('v1.source.release.get_by_id',
# risotto_context,
# release_id=as_release_id)
await self.generate(risotto_context,
servermodel_name,
servermodel_id,
dependencies,
generate_cache)
sm_dict = {'servermodel_name': servermodel_name,
'servermodel_description': servermodel_description,
'servermodel_parents_id': servermodel_parents_id,
'servermodel_applicationservice_id': applicationservice_id,
'release_id': release_id,
'servermodel_id': servermodel_id}
return sm_dict
def parse_parents(self,
servermodels: Dict,
servermodel: Dict,
parents: List=None) -> List:
if parents is None:
parents = [servermodel['name']]
parent = servermodel['parent']
if parent in servermodels:
parents.append(parent)
self.parse_parents(servermodels, servermodels[parent], parents)
return parents
@register('v1.applicationservice.updated')
async def applicationservice_updated(self,
risotto_context: Context,
applicationservice_id):
# FIXME applicationservices qui depend de ce services => updated
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id, ServermodelApplicationServiceId as servermodel_applicationservice_id
FROM Servermodel
WHERE ServermodelApplicationServiceId = $1
'''
servermodel = await risotto_context.connection.fetchrow(sql,
applicationservice_id)
if servermodel is not None:
servermodel_name = servermodel['servermodel_name']
servermodel_id = servermodel['servermodel_id']
release_id = servermodel['release_id']
applicationservice = await self.call('v1.applicationservice.get_by_id',
risotto_context,
applicationservice_id=applicationservice_id)
dependencies = applicationservice['applicationservice_dependencies']
await self.generate(risotto_context,
servermodel_name,
servermodel_id,
dependencies,
None)
await self.publish('v1.servermodel.updated',
risotto_context,
**servermodel)
@register('v1.servermodel.dataset.updated')
async def servermodel_dataset_updated(self,
risotto_context: Context,
source_name: str,
release_distribution: int):
release = await self.call('v1.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
release_id = release['release_id']
generate_cache = {'applicationservice': {},
'release_id': {release['release_id']: (release['source_name'],
release['release_name'])}}
servermodel_path = join(self.source_root_path,
source_name,
release['release_name'],
'servermodel')
servermodels = {}
for servermodel in listdir(servermodel_path):
if not servermodel.endswith('.yml'):
continue
servermodel_description_path = join(servermodel_path, servermodel)
try:
with open(servermodel_description_path, 'r') as servermodel_yml:
servermodel_description = load(servermodel_yml,
Loader=SafeLoader)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f'Error while reading {servermodel_description_path}: {err}'))
servermodels[servermodel_description['name']] = servermodel_description
servermodels[servermodel_description['name']]['done'] = False
for servermodel in servermodels.values():
if not servermodel['done']:
# parent needs to create before child, so retrieve all parents
parents = self.parse_parents(servermodels,
servermodel)
parents.reverse()
servermodel_parent = []
for new_servermodel in parents:
if not servermodels[new_servermodel]['done']:
servermodel_description = servermodels[new_servermodel]
parent = servermodel_description['parent']
if not servermodel_parent and parent is not None:
servermodel_parent = [await self._servermodel_describe(risotto_context,
parent,
release_id)]
# link application service with this servermodel
dependencies = []
for depend in servermodels[new_servermodel]['applicationservices']:
applicationservice = await self.call('v1.applicationservice.describe',
risotto_context,
applicationservice_name=depend,
source_name=source_name,
release_distribution=release_distribution)
dependencies.append(applicationservice['applicationservice_id'])
sm_name = servermodel_description['name']
sm_description = servermodel_description['description']
try:
servermodel_ob = await self._servermodel_create(risotto_context,
sm_name,
sm_description,
servermodel_parent,
dependencies,
release_id,
generate_cache)
await self.publish('v1.servermodel.created',
risotto_context,
**servermodel_ob)
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
raise ExecutionError(_(f"Error while injecting servermodel {sm_name} in database: {err}"))
servermodel_parent = [servermodel_ob]
servermodel_description['done'] = True
return {'retcode': 0, 'returns': _('Servermodels successfully loaded')}
@register('v1.servermodel.list')
async def servermodel_list(self,
risotto_context: Context,
source_id: int):
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id, ServermodelApplicationServiceId as servermodel_applicationservice_id
FROM Servermodel
'''
servermodels = await risotto_context.connection.fetch(sql)
return [dict(r) for r in servermodels]
@register('v1.servermodel.describe')
async def servermodel_describe(self,
risotto_context: Context,
servermodel_name,
source_name,
release_distribution) -> Dict:
release = await self.call('v1.source.release.describe',
risotto_context,
source_name=source_name,
release_distribution=release_distribution)
return await self._servermodel_describe(risotto_context,
servermodel_name,
release['release_id'])
async def _servermodel_describe(self,
risotto_context,
servermodel_name,
release_id):
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id, ServermodelApplicationServiceId as servermodel_applicationservice_id
FROM Servermodel
WHERE ServermodelName=$1 AND ServermodelReleaseId=$2
'''
servermodel = await risotto_context.connection.fetchrow(sql,
servermodel_name,
release_id)
if not servermodel:
raise Exception(_(f'"{servermodel_name}" is not a valid name for a servermodel in source "{source_name}" and release "{release_distribution}"'))
return dict(servermodel)
@register('v1.servermodel.create', notification='v1.servermodel.created')
async def create_servermodel(self,
risotto_context: Context,
servermodel_name: str,
servermodel_description: str,
servermodel_parents_name: List[int],
servermodel_parents_source_name: str,
servermodel_parents_release_distribution: str) -> Dict:
release = await self.call('v1.source.release.describe',
risotto_context,
source_name=servermodel_parents_source_name,
release_distribution=servermodel_parents_release_distribution)
release_id = release['release_id']
servermodel_parents = []
for servermodel_parent_name in servermodel_parents_name:
servermodel_parents.append(await self._servermodel_describe(risotto_context,
servermodel_parent_name,
release_id))
return await self._servermodel_create(risotto_context,
servermodel_name,
servermodel_description,
servermodel_parents,
[],
self.internal_release_id,
None)

View File

@ -6,19 +6,21 @@ from tiramisu import Storage
from ...http import register as register_http
from ...config import DEBUG
from ...context import Context
from ...utils import _
from ...error import CallError
from .storage import storage_server, storage_servermodel
from ...controller import Controller
from ...register import register
from ...dispatcher import dispatcher
from ...config import get_config
class Risotto(Controller):
def __init__(self):
def __init__(self,
test):
self.modify_storage = Storage(engine='dictionary')
self.internal_source_name = get_config()['servermodel']['internal_source']
self.internal_distribution_name = get_config()['servermodel']['internal_distribution']
def get_storage(self,
type: str):
@ -64,52 +66,97 @@ class Risotto(Controller):
'mode': session['mode'],
'debug': session['debug']}
@register(['v1.session.server.start', 'v1.session.servermodel.start'], None)
async def start_session(self,
risotto_context: Context,
id: int) -> Dict:
""" start a new config session for a server or a servermodel
@register('v1.session.server.start')
async def start_session_server(self,
risotto_context: Context,
server_name: str) -> Dict:
""" start a new config session for a server
"""
type = risotto_context.message.rsplit('.', 2)[-2]
config_module = dispatcher.get_service('config')
if type == 'server':
if id not in config_module.server:
raise Exception(_(f'cannot find {type} with id {id}'))
config = config_module.server[id]['server']
else:
if id not in config_module.servermodel:
raise Exception(_(f'cannot find {type} with id {id}'))
config = config_module.servermodel[id]
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
if not server or server['server_id'] not in config_module.server:
raise Exception(_(f'cannot find server with name {server_name}'))
id = server['server_id']
config = config_module.server[id]['server_to_deploy']
storage = self.get_storage(type)
storage = self.get_storage('server')
# check if a session already exists
sessions = storage.get_sessions()
for session in sessions.values():
if sess['id'] == id:
if sess['username'] == risotto_context.username:
for sess_id, session in sessions.items():
if session['id'] == id:
if session['username'] == risotto_context.username:
# same user so returns it
return self.format_session(session['session_id'], session)
return self.format_session(sess_id,
session)
else:
raise CallError(_(f'{username} already edits this configuration'))
raise Exception(_(f'{username} already edits this configuration'))
# create a new session
while True:
session_id = 'z' + hexlify(urandom(23)).decode()
if not session_id in sessions:
break
storage.add_session(session_id,
config,
id,
risotto_context.username,
self.modify_storage)
await storage.add_session(session_id,
config,
id,
risotto_context.username,
self.modify_storage)
# return session's information
return self.get_session_informations(risotto_context,
session_id,
type)
'server')
@register(['v1.session.server.list', 'v1.session.servermodel.list'], None)
@register('v1.session.servermodel.start')
async def start_session_servermodel(self,
risotto_context: Context,
servermodel_name: str) -> Dict:
""" start a new config session for a server or a servermodel
"""
config_module = dispatcher.get_service('config')
servermodel = await self.call('v1.servermodel.describe',
risotto_context,
servermodel_name=servermodel_name,
source_name=self.internal_source_name,
release_distribution=self.internal_distribution_name)
if not servermodel or servermodel['servermodel_id'] not in config_module.servermodel:
raise Exception(_(f'cannot find servermodel with name {servermodel_name}'))
id = servermodel['servermodel_id']
config = config_module.servermodel[id]
storage = self.get_storage('servermodel')
# check if a session already exists
sessions = storage.get_sessions()
for sess_id, session in sessions.items():
if session['id'] == id:
if session['username'] == risotto_context.username:
# same user so returns it
return self.format_session(sess_id,
session)
else:
raise Exception(_(f'{username} already edits this configuration'))
# create a new session
while True:
session_id = 'z' + hexlify(urandom(23)).decode()
if not session_id in sessions:
break
await storage.add_session(session_id,
config,
id,
risotto_context.username,
self.modify_storage)
# return session's information
return self.get_session_informations(risotto_context,
session_id,
'servermodel')
@register(['v1.session.server.list', 'v1.session.servermodel.list'])
async def list_session_server(self,
risotto_context: Context) -> Dict:
type = risotto_context.message.rsplit('.', 2)[-2]
@ -117,7 +164,7 @@ class Risotto(Controller):
return [self.format_session(session_id, session) for session_id, session in storage.get_sessions().items()]
@register(['v1.session.server.filter', 'v1.session.servermodel.filter'], None)
@register(['v1.session.server.filter', 'v1.session.servermodel.filter'])
async def filter_session(self,
risotto_context: Context,
session_id: str,
@ -134,17 +181,17 @@ class Risotto(Controller):
namespace)
if mode is not None:
if mode not in ('basic', 'normal', 'expert'):
raise CallError(f'unknown mode {mode}')
storage.set_config_mode(session_id,
mode)
raise Exception(f'unknown mode {mode}')
await storage.set_config_mode(session_id,
mode)
if debug is not None:
storage.set_config_debug(session_id,
debug)
await storage.set_config_debug(session_id,
debug)
return self.get_session_informations(risotto_context,
session_id,
type)
@register(['v1.session.server.configure', 'v1.session.servermodel.configure'], None)
@register(['v1.session.server.configure', 'v1.session.servermodel.configure'])
async def configure_session(self,
risotto_context: Context,
session_id: str,
@ -158,28 +205,30 @@ class Risotto(Controller):
session_id,
type)
# if multi and not follower the value is in fact in value_multi
option = session['option'].option(name).option
if option.ismulti() and not option.isfollower():
# FIXME option = session['option'].option(name).option
option = session['config'].option(name).option
if await option.ismulti() and not await option.isfollower():
value = value_multi
namespace = session['namespace']
update = {'name': f'{namespace}.{name}',
#FIXME namespace = session['namespace']
#FIXME update = {'name': f'{namespace}.{name}',
update = {'name': name,
'action': action,
'value': value}
if index is not None:
update['index'] = index
updates = {'updates': [update]}
ret = session['option'].updates(updates)
ret = await session['option'].updates(updates)
if update['name'] in ret:
for val in ret[update['name']][index]:
if isinstance(val, ValueError):
raise CallError(val)
raise Exception(val)
ret = {'session_id': session_id,
'name': name}
if index is not None:
ret['index'] = index
return ret
@register(['v1.session.server.validate', 'v1.session.servermodel.validate'], None)
@register(['v1.session.server.validate', 'v1.session.servermodel.validate'])
async def validate_session(self,
risotto_context: Context,
session_id: str) -> Dict:
@ -188,23 +237,27 @@ class Risotto(Controller):
session_id,
type)
try:
session['config'].forcepermissive.option(session['namespace']).value.dict()
await session['config'].forcepermissive.option(session['namespace']).value.dict()
except Exception as err:
raise CallError(str(err))
raise Exception(str(err))
if type == 'server':
mandatories = list(session['config'].forcepermissive.value.mandatory())
config = session['config']
await config.property.read_only()
mandatories = list(await config.value.mandatory())
await config.property.read_write()
if mandatories:
# FIXME mandatories = [mandatory.split('.', 1)[1] for mandatory in mandatories]
if len(mandatories) == 1:
mandatories = mandatories[0]
msg = _('the parameter "--{mandatories}" is mandatory')
msg = _(f'the parameter "--{mandatories}" is mandatory')
else:
mandatories = '", "--'.join(mandatories)
msg = _('parameters "{mandatories}" are mandatories')
raise CallError(msg)
msg = _(f'parameters "--{mandatories}" are mandatories')
raise Exception(msg)
return self.format_session(session_id,
session)
@register(['v1.session.server.get', 'v1.session.servermodel.get'], None)
@register(['v1.session.server.get', 'v1.session.servermodel.get'])
async def get_session_server(self,
risotto_context: Context,
session_id: str,
@ -215,12 +268,14 @@ class Risotto(Controller):
type)
info = self.format_session(session_id, session)
if name is not None:
info['content'] = {name: session['option'].option(name).value.get()}
content = {name: await session['config'].option(name).value.get()}
else:
info['content'] = session['option'].value.dict()
content = await session['option'].value.dict(fullpath=True,
leader_to_list=True)
info['content'] = content
return info
@register(['v1.session.server.stop', 'v1.session.servermodel.stop'], None)
@register(['v1.session.server.stop', 'v1.session.servermodel.stop'])
async def stop_session(self,
risotto_context: Context,
session_id: str,
@ -232,14 +287,14 @@ class Risotto(Controller):
id_ = session['id']
config_module = dispatcher.get_service('config')
if type == 'server':
config = config_module.server[id_]['server']
config = config_module.server[id_]['server_to_deploy']
else:
config = config_module.servermodel[id_]
if save:
modif_config = session['config']
config.value.importation(modif_config.value.exportation())
config.permissive.importation(modif_config.permissive.exportation())
storage.del_session(session_id)
await config.value.importation(await modif_config.value.exportation())
await config.permissive.importation(await modif_config.permissive.exportation())
await storage.del_session(session_id)
return self.format_session(session_id, session)
@register_http('v1', '/config/server/{session_id}')
@ -249,7 +304,7 @@ class Risotto(Controller):
session_id: str) -> Dict:
session = storage_server.get_session(session_id,
risotto_context.username)
return session['option'].dict(remotable='all')
return await session['option'].dict(remotable='all')
@register_http('v1', '/config/servermodel/{session_id}')
async def get_servermodel_api(self,
@ -258,4 +313,4 @@ class Risotto(Controller):
session_id: str) -> Dict:
session = storage_servermodel.get_session(session_id,
risotto_context.username)
return session['option'].dict(remotable='all')
return await session['option'].dict(remotable='all')

View File

@ -15,26 +15,26 @@ class Storage(object):
def __init__(self):
self.sessions = {}
def add_session(self,
session_id: int,
orig_config: Config,
server_id: int,
username: str,
config_storage):
async def add_session(self,
session_id: int,
orig_config: Config,
server_id: int,
username: str,
config_storage):
prefix_id = f'{session_id}_'
config_name = self.get_config_name(server_id)
config_id = f'{prefix_id}{config_name}'
# copy Config and all it's parents
meta = orig_config.config.deepcopy(session_id=config_id,
storage=config_storage,
metaconfig_prefix=prefix_id)
meta = await orig_config.config.deepcopy(session_id=config_id,
storage=config_storage,
metaconfig_prefix=prefix_id)
# retrieve the copied config (not metaconfig)
config = meta
while True:
try:
children = list(config.config.list())
children = list(await config.config.list())
if not children:
# it's an empty metaconfig
break
@ -42,10 +42,10 @@ class Storage(object):
except:
# it's a config, so no "list" method
break
config.property.read_write()
await config.property.read_write()
# set the default owner
self.set_owner(config,
username)
await self.set_owner(config,
username)
# store it
self.sessions[session_id] = {'config': config,
@ -54,34 +54,34 @@ class Storage(object):
'id': server_id,
'timestamp': int(time.time()),
'username': username}
self.set_config_mode(session_id,
await self.set_config_mode(session_id,
'normal')
self.set_config_debug(session_id,
False)
await self.set_config_debug(session_id,
False)
self.set_namespace(session_id,
'creole')
def set_config_mode(self,
id: int,
mode: str):
async def set_config_mode(self,
id: int,
mode: str):
""" Define which edition mode to select
"""
config = self.sessions[id]['config']
for mode_level in modes.values():
if modes[mode] < mode_level:
config.property.add(mode_level.name)
await config.property.add(mode_level.name)
else:
config.property.pop(mode_level.name)
await config.property.pop(mode_level.name)
self.sessions[id]['mode'] = mode
def set_config_debug(self, id_, is_debug):
async def set_config_debug(self, id_, is_debug):
""" Enable/Disable debug mode
"""
config = self.sessions[id_]['config']
if is_debug:
config.property.pop('hidden')
await config.property.pop('hidden')
else:
config.property.add('hidden')
await config.property.add('hidden')
self.sessions[id_]['debug'] = is_debug
def set_namespace(self,
@ -97,14 +97,27 @@ class Storage(object):
session_id: int,
username: str) -> Dict:
if session_id not in self.sessions:
raise Exception(f'the session {id} not exists')
raise Exception(f'the session "{session_id}" not exists')
session = self.sessions[session_id]
if username != session['username']:
raise NotAllowedError()
return session
def del_session(self,
id: int):
async def del_session(self,
id: int):
config = self.sessions[id]['meta']
while True:
try:
children = list(await config.config.list())
if not children:
# it's an empty metaconfig
break
config = children[0]
await config.session.reset()
except:
# it's a config, so no "list" method
break
await self.sessions[id]['meta'].session.reset()
del self.sessions[id]
@ -113,10 +126,10 @@ class StorageServer(Storage):
server_id: int):
return f'std_{server_id}'
def set_owner(self,
config: Config,
username: str):
config.owner.set(username)
async def set_owner(self,
config: Config,
username: str):
await config.owner.set(username)
class StorageServermodel(Storage):
@ -124,10 +137,10 @@ class StorageServermodel(Storage):
server_id: int):
return f'v_{server_id}'
def set_owner(self,
config: Config,
username: str):
config.owner.set('servermodel_' + username)
async def set_owner(self,
config: Config,
username: str):
await config.owner.set('servermodel_' + username)
storage_server = StorageServer()

View File

@ -0,0 +1 @@
from .source import Risotto

View File

@ -0,0 +1,150 @@
from typing import Dict, List
from ...controller import Controller
from ...register import register
from ...context import Context
import requests
import yaml
import os
from ...utils import _
from ...config import get_config
class Risotto(Controller):
@register('v1.source.create')
async def source_create(self,
risotto_context: Context,
source_name: str,
source_url: str) -> Dict:
source_upsert = """INSERT INTO Source(SourceName, SourceURL) VALUES ($1, $2)
ON CONFLICT (SourceName) DO UPDATE SET SourceURL = $2
RETURNING SourceId
"""
# If given url is not 'none' (a.k.a internal source)
# Look for file releases.yml at given url
# If such a file exists, consider source a valid one and create source in database.
if source_url != 'none':
try:
releases = yaml.load(requests.get(source_url.rstrip('/') + '/releases.yml').content, Loader=yaml.SafeLoader)
except requests.exceptions.ConnectionError as err:
raise Exception(_('Invalid URL'))
except yaml.scanner.ScannerError as err:
raise Exception(_('Invalid releases.yml file'))
except:
raise Exception(_('Invalid source'))
else:
releases = {'1.0.0': {'distribution': 'last'}}
os.makedirs(os.path.join(get_config().get('source').get('root_path'), source_name), exist_ok=True)
with open(os.path.join(get_config().get('source').get('root_path'), source_name, 'releases.yml'), 'w') as release_file:
yaml.dump(releases, release_file)
source_id = await risotto_context.connection.fetchval(source_upsert,
source_name,
source_url)
return {'source_name': source_name,
'source_url': source_url,
'source_id': source_id}
@register('v1.source.describe')
async def source_describe(self,
risotto_context: Context,
source_name: str) -> Dict:
source_get = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
FROM Source
WHERE SourceName = $1
"""
source = await risotto_context.connection.fetchrow(source_get,
source_name)
if not source:
raise Exception(_(f'unknown source with name {source_name}'))
return dict(source)
@register('v1.source.list')
async def source_list(self,
risotto_context: Context) -> List[Dict]:
source_list = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
FROM Source
"""
result = await risotto_context.connection.fetch(source_list)
return [dict(r) for r in result]
@register('v1.source.dataset.update')
async def version_update(self,
risotto_context: Context,
source_id: int,
release_name: str):
# source.release.create is an upsert, do not using it
release_insert = """INSERT INTO Release(ReleaseName, ReleaseSourceId) VALUES ($1, $2)
RETURNING ReleaseId
"""
release_id = await risotto_context.connection.fetchval(release_insert,
release_name,
source_id)
return {'release_id': release_id,
'release_name': release_name}
@register('v1.source.release.create')
async def source_release_create(self,
risotto_context: Context,
source_name: str,
release_name: str,
release_distribution: str) -> Dict:
source_get = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
FROM Source
WHERE SourceName = $1
"""
release_upsert = """INSERT INTO Release(ReleaseName, ReleaseSourceId, ReleaseDistribution) VALUES ($1, $2, $3)
ON CONFLICT (ReleaseName, ReleaseSourceId) DO UPDATE SET ReleaseName = $1
RETURNING ReleaseId
"""
source_obj = await risotto_context.connection.fetchrow(source_get,
source_name)
if not source_obj:
raise Exception(_(f'unable to find a source with name {source_name}'))
source = dict(source_obj)
release_id = await risotto_context.connection.fetchval(release_upsert,
release_name,
source['source_id'],
release_distribution)
del source['source_id']
source['release_id'] = release_id
source['release_name'] = release_name
source['release_distribution'] = release_distribution
return source
@register('v1.source.release.list')
async def release_list(self,
risotto_context,
source_name: str) -> Dict:
release_query = """SELECT ReleaseId as release_id, SourceName as source_name, SourceURL as source_url, ReleaseName as release_name, ReleaseDistribution as release_distribution
FROM Release, Source
WHERE Source.SourceName=$1 AND Source.SourceId=Release.ReleaseSourceId"""
result = await risotto_context.connection.fetch(release_query,
source_name)
return [dict(r) for r in result]
@register('v1.source.release.describe')
async def release_describe(self,
risotto_context,
source_name: str,
release_distribution: str) -> Dict:
release_query = """SELECT ReleaseId as release_id, SourceName as source_name, SourceURL as source_url, ReleaseName as release_name, ReleaseDistribution as release_distribution
FROM Release, Source
WHERE Source.SourceName=$1 AND Source.SourceId=Release.ReleaseSourceId AND Release.ReleaseDistribution=$2"""
result = await risotto_context.connection.fetchrow(release_query,
source_name,
release_distribution)
if not result:
raise Exception(_(f'unknown release distribution {release_distribution} in source {source_name}'))
return dict(result)
@register('v1.source.release.get_by_id')
async def release_get_by_id(self,
risotto_context: Context,
release_id: int) -> Dict:
release_query = """SELECT ReleaseId as release_id, SourceName as source_name, SourceURL as source_url, ReleaseName as release_name, ReleaseDistribution as release_distribution
FROM Release, Source
WHERE Release.ReleaseId = $1 AND Source.SourceId = Release.ReleaseSourceId"""
result = await risotto_context.connection.fetchrow(release_query,
release_id)
if not result:
raise Exception(_(f'unknown release id {release_id}'))
return dict(result)

View File

@ -1,49 +1,66 @@
from os import mkdir
from os.path import isdir, join
from shutil import rmtree
from typing import Dict
from rougail.template import generate
from tiramisu import Storage
from ...config import ROOT_CACHE_DIR, CONFIGURATION_DIR, TEMPLATE_DIR, TMP_DIR
from ...config import CONFIGURATION_DIR, TMP_DIR, get_config
from ...controller import Controller
from ...register import register
from ...dispatcher import dispatcher
from ...utils import _
class Risotto(Controller):
def __init__(self):
def __init__(self,
test: bool) -> None:
self.storage = Storage(engine='dictionary')
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
@register('v1.template.generate', None)
@register('v1.template.generate')
async def template_get(self,
server_id: int):
risotto_context,
server_name: str) -> Dict:
# get informations for server
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
servermodel_id = server['server_servermodel_id']
# verify if server has deployed configuration
config_module = dispatcher.get_service('config')
server = config_module.server[server_id]
config = meta = server['server'].config.deepcopy(storage=self.storage)
while True:
try:
children = list(config.config.list())
except:
break
if children:
config = children[0]
else:
break
print(config.value.dict())
configurations_dir = join(CONFIGURATION_DIR,
str(server_id))
if isdir(configurations_dir):
rmtree(configurations_dir)
mkdir(configurations_dir)
tmp_dir = join(TMP_DIR, str(server_id))
if isdir(tmp_dir):
rmtree(tmp_dir)
mkdir(tmp_dir)
templates_dir = join(TEMPLATE_DIR, str(server_id))
generate(config,
server['funcs_file'],
templates_dir,
tmp_dir,
configurations_dir)
return {'server_id': server_id,
export = await server['server'].value.exportation()
if not export[0]:
raise Exception(_(f'configuration for server "{server_name}" is empty, you should deploy it first'))
# copy deployed configuration
async with await server['server'].config.deepcopy(storage=self.storage) as config:
meta = config
while True:
try:
children = list(await config.config.list())
except:
break
if children:
config = children[0]
else:
break
configurations_dir = join(CONFIGURATION_DIR,
str(server_id))
if isdir(configurations_dir):
rmtree(configurations_dir)
mkdir(configurations_dir)
tmp_dir = join(TMP_DIR, str(server_id))
if isdir(tmp_dir):
rmtree(tmp_dir)
mkdir(tmp_dir)
templates_dir = join(self.cache_root_path, str(servermodel_id), 'templates')
await generate(config,
server['funcs_file'],
templates_dir,
tmp_dir,
configurations_dir)
del meta, config
# FIXME del session !
return {'server_name': server_name,
'template_dir': configurations_dir}

View File

@ -0,0 +1 @@
from .uri import Risotto

View File

@ -0,0 +1,117 @@
from typing import Dict, List
from ...controller import Controller
from ...register import register
from ...context import Context
from ...utils import _
class Risotto(Controller):
async def on_join(self,
risotto_context):
for uri in ['v1.applicationservice.create',
'v1.applicationservice.dataset.updated',
'v1.applicationservice.dependency.add',
'v1.server.create',
'v1.servermodel.create',
'v1.servermodel.dataset.updated',
'v1.session.server.start',
'v1.source.create',
'v1.source.dataset.update',
'v1.source.release.create',
'v1.template.generate',
'v1.uri.role.join',
'v1.uri.role.list',
'v1.user.create',
'v1.user.delete',
'v1.user.list',
'v1.user.role.create',
'v1.config.configuration.server.get',
'v1.user.role.list']:
try:
await self._uri_role_join(risotto_context,
role_name='administrator',
uri_name=uri)
except:
pass
for uri in ['v1.applicationservice.describe',
'v1.server.describe',
'v1.server.list',
'v1.servermodel.list',
'v1.session.server.configure',
'v1.session.server.filter',
'v1.session.server.get',
'v1.session.server.list',
'v1.session.servermodel.configure',
'v1.session.servermodel.filter',
'v1.session.servermodel.get',
'v1.session.servermodel.list',
'v1.session.servermodel.start',
'v1.session.servermodel.stop',
'v1.session.servermodel.validate',
'v1.session.server.stop',
'v1.session.server.validate',
'v1.source.describe',
'v1.source.list',
'v1.source.release.list']:
try:
await self._uri_role_join(risotto_context,
role_name='all',
uri_name=uri)
except:
pass
for uri in ['v1.server.describe',
'v1.applicationservice.dependency.add',
'v1.config.configuration.server.get',
'v1.config.configuration.server.deploy',
'v1.session.server.start',
'v1.template.generate']:
try:
await self._uri_role_join(risotto_context,
role_name='server_rw',
uri_name=uri)
except:
pass
@register('v1.uri.role.join')
async def uri_role_join(self,
risotto_context: Context,
role_name: str,
uri_name: str) -> Dict:
return await self._uri_role_join(risotto_context,
role_name,
uri_name)
async def _uri_role_join(self,
risotto_context: Context,
role_name: str,
uri_name: str) -> Dict:
# Verify if user exists and get ID
sql = '''
SELECT URIId
FROM URI
WHERE URIName = $1
'''
uri_id = await risotto_context.connection.fetchval(sql,
uri_name)
if uri_id is None:
raise Exception(_(f'unable to find message {uri_name}'))
sql = '''
INSERT INTO RoleURI(RoleName, URIId)
VALUES ($1,$2)
ON CONFLICT DO NOTHING
'''
uri_id = await risotto_context.connection.fetchrow(sql,
role_name,
uri_id)
return {'role_name': role_name,
'uri_name': uri_name}
@register('v1.uri.role.list')
async def uri_role_list(self,
risotto_context: Context) -> List[Dict]:
sql = '''
SELECT RoleName as role_name, URI.URIName as uri_name
FROM RoleURI, URI
WHERE RoleURI.URIId = URI.URIId
'''
return [dict(r) for r in await risotto_context.connection.fetch(sql)]

View File

@ -0,0 +1 @@
from .user import Risotto

Some files were not shown because too many files have changed in this diff Show More