Compare commits

...

40 Commits

Author SHA1 Message Date
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
3b31f092bd add session tests 2019-12-07 16:21:20 +01:00
3c5285a7d2 add test for config's service 2019-12-06 07:14:24 +01:00
110 changed files with 4107 additions and 1254 deletions

View File

@ -18,3 +18,55 @@ echo "127.0.0.1 auth.example.com manager.example.com test1.example.com test2.exa
docker run -d --add-host reload.example.com:127.0.0.1 -p 80:80 coudot/lemonldap-ng
```
Démarrer un serveur postgresql de test
```
podman pull docker.io/library/postgres:11-alpine
podman run -dt -p 5432:5432 postgres:11-alpine
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
```
Gestion de la base de données avec Sqitch
```
cpanm --quiet --notest App::Sqitch
sqitch init risotto --uri https://forge.cadoles.com/Infra/risotto --engine pg
```
Commande :
# 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;
# 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 server
./script/cucchiaiata server.create -s test -d description -m eolebase -n eole -r last
# Configure the server
./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 --creole.reseau.unbound_route_address 192.168.1.2 --creole.serveur_dns.unbound_allowed_client_cidr 192.168.1.0/24 --creole.serveur_dns.unbound_local_zones cadoles.com
./script/cucchiaiata session.server.configure -s $S --creole.reseau.unbound_domain_name test.cadoles.com
./script/cucchiaiata session.server.filter -s $S -n unbound
./script/cucchiaiata session.server.configure -s $S --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.hostname_cadoles_com toto titi --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.ip_cadoles_com 0 192.168.1.25 --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.type_cadoles_com 1 CNAME --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.cname_cadoles_com 1 toto.cadoles.com
./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
# 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

View File

@ -0,0 +1,33 @@
---
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: []
release_id:
type: Number
shortarg: r
description: |
Identifiant de la version associée au service applicatif.
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,23 @@
---
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.
release_id:
type: Number
shortarg: r
description: |
Identifiant de la 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

@ -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,23 +4,17 @@ 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.
deploy:
server_name:
type: String
ref: Server.ServerName
description: Nom du serveur.
deployed:
type: Boolean
description: Configuration de type déployée.
default: true
response:
type: ConfigConfiguration

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.
deploy:
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,35 @@
---
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.
source_name:
type: String
shortarg: n
ref: Source.SourceName
description: Nom de la source.
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

@ -5,11 +5,6 @@ description: Des modèles de serveur ont été créés.
pattern: event
public: false
domain: servermodel-domain
parameters:
servermodels:
type: '[]Servermodel'
description: Informations sur les modèles de serveur créés.
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

@ -0,0 +1,17 @@
---
uri: servermodel.get_by_id
description: Retourne les attributs détaillés d'un modèle de serveur suivant son identifiant.
pattern: rpc
parameters:
servermodel_id:
type: Number
shortarg: s
description: Identifiant du modèle de serveur à récupérer.
ref: Servermodel.ServermodelId
response:
type: Servermodel
description: Description du modèle de serveur.

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,11 +5,6 @@ description: Des modèles de serveur ont été modifiés.
pattern: event
public: false
domain: servermodel-domain
parameters:
servermodels:
type: '[]Servermodel'
description: Informations sur les modèles de serveur modifiés.
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

@ -2,20 +2,21 @@
uri: session.server.get
description: |
Configure le server.
Récupérer la configuration du server.
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String
ref: Config.SessionId
shortarg: s
description: Identifiant de la configuration.
name:
type: String
shortarg: n
description: Nom de la variable.
default: null
response:
type: Session

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
@ -18,6 +14,6 @@ parameters:
description: Identifiant de la session.
response:
type: SessionConfigurationStatus
type: Session
description: Statut de la configuration.

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,16 +6,17 @@ description: |
pattern: rpc
public: true
domain: session-domain
parameters:
session_id:
type: String
ref: Config.SessionId
shortarg: s
description: Identifiant de la configuration.
name:
type: String
shortarg: n
description: Nom de la variable.
default: null
response:
type: Session

View File

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

View File

@ -5,17 +5,22 @@ 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.
source_name:
type: String
shortarg: n
description: Nom de la source.
ref: Source.SourceName
release_distribution:
type: String
shortarg: r
description: Nom de la distribution.
ref: Source.ReleaseDistribution
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
@ -18,6 +14,6 @@ parameters:
description: Identifiant de la session.
response:
type: SessionConfigurationStatus
type: Session
description: Statut de la configuration.

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,22 @@
---
uri: source.release.get_by_distribution
description: |
Retourne version suivant le nom de la distribution.
pattern: rpc
parameters:
source_id:
type: Number
shortarg: s
description: ID de la source.
release_distribution:
type: String
shortarg: r
description: Distribution de la version.
response:
type: 'Release'
description: La version disponibles.

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,8 +3,15 @@ title: ConfigConfiguration
type: object
description: Description de la configuration.
properties:
server_name:
type: string
description: Nom du serveur.
ref: Server.ServerName
deployed:
type: boolean
description: La configuration est déployée.
configuration:
type: File
type: object
description: Détail de la configuration au format JSON.
required:
- configuration

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,28 @@ 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:
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: File
description: Contenu du schema.
probes:
type: File
description: Informations sur les sondes.
creolefuncs:
type: File
description: Fonctions Creole.
conffiles:
type: File
description: Fichiers creole au format tar encodé base64
required:
- servermodelid
- servermodelname
- servermodeldescription
- servermodelsubreleaseid
- sourceid
- subreleasename
- servermodel_id
- servermodel_name
- servermodel_description
- release_id

View File

@ -1,24 +0,0 @@
---
title: SessionConfigurationStatus
type: object
description: Statut de la configuration.
properties:
session_id:
type: string
description: ID de la session.
ref: Config.SessionId
status:
type: string
description: Statut de la configuration (peut être ok, error, incomplete)
message:
type: string
description: Message d'erreur si la configuration a le statut error.
mandatories:
type: array
items:
type: string
description: Liste des variables obligatoires non renseignées si la configuration a le statut incomplete.
required:
- session_id
- status

View File

@ -12,9 +12,6 @@ properties:
index:
type: number
description: Index de la variable a modifier.
status:
type: string
description: Status de la modification.
message:
type: string
description: Message d'erreur.

View File

@ -27,7 +27,7 @@ properties:
type: boolean
description: La configuration est en mode debug.
content:
type: file
type: object
description: Contenu de la configuration.
required:
- session_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

108
script/database_manager.py Normal file
View File

@ -0,0 +1,108 @@
import asyncpg
import asyncio
from risotto.config import get_config
VERSION_INIT = """
-- 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
);
"""
async def main():
db_conf = get_config().get('database')
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user'))
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,4 @@
from .http import get_app
# just to register every route
from . import services as _services
__ALL__ = ('get_app',)

View File

@ -1,10 +1,32 @@
HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages'
ROOT_CACHE_DIR = 'cache'
DEBUG = True
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'
import os
from pathlib import PurePosixPath
CURRENT_PATH = PurePosixPath(__file__)
def get_config():
return {'database': {'host': 'localhost',
'port': 5432,
'dbname': 'risotto',
'user': 'risotto',
'password': 'risotto',
},
'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,
'rougail_dtd_path': '../rougail/data/creole.dtd',
'admin_user': DEFAULT_USER},
'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'}
}

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,74 @@
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()
config = await Config(response,
display_name=lambda self, dyn_name: self.impl_getname())
await config.property.read_write()
try:
for key, value in ret.items():
config.option(key).value.set(value)
await 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)
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}"')
log.error_msg(risotto_context, kwargs, err)
await log.error_msg(risotto_context, kwargs, err)
raise CallError(str(err))
config.property.read_only()
mandatories = list(config.value.mandatory())
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:
config.value.dict()
await 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)
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 +77,71 @@ 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:
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)
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]['functions']:
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
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
await function(self.injected_self[function_obj['module']], **kw)
except Exception as err:
if DEBUG:
print_exc()
log.error_msg(risotto_context, kwargs, err, info_msg)
else:
log.info_msg(risotto_context, kwargs, info_msg)
# notification
if obj.get('notification'):
notif_version, notif_message = obj['notification'].split('.', 1)
await self.publish(notif_version,
notif_message,
risotto_context,
**returns)
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:
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)
class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher):
@ -202,46 +166,181 @@ 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()
config = await Config(self.option)
await config.property.read_write()
# set message's option
config.option('message').value.set(risotto_context.message)
await 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)
await subconfig.option(key).value.set(value)
except AttributeError:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise AttributeError(_(f'unknown parameter "{key}"'))
raise ValueError(_(f'unknown parameter in "{uri}": "{key}"'))
# check mandatories options
config.property.read_only()
mandatories = list(config.value.mandatory())
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: {mand}'))
# return the config
return config
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)
try:
config_arguments = await self.load_kwargs_to_config(risotto_context,
f'{version}.{message}',
kwargs,
check_role)
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()
await log.error_msg(risotto_context, kwargs, err)
if risotto_context.type == 'rpc':
raise err
return
# 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 get_config().get('global').get('debug'):
print_exc()
await log.error_msg(risotto_context,
kwargs,
err)
if risotto_context.type == 'rpc':
raise CallError(str(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,19 +1,23 @@
from aiohttp.web import Application, Response, get, post, HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
from tiramisu import Config
from json import dumps
from traceback import print_exc
from tiramisu import Config
from .dispatcher import dispatcher
from .utils import _
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 traceback import print_exc
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
@ -49,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}))
@ -81,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)]
config = await Config(get_messages(load_shortarg=True, uris=uris)[1])
await config.property.read_write()
tiramisu = await config.option.dict(remotable='none')
return tiramisu
@ -96,18 +116,17 @@ async def get_app(loop):
""" build all routes
"""
global extra_routes
load_services()
app = Application(loop=loop)
routes = []
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 ========'))
@ -123,7 +142,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,12 +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 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
@ -19,36 +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)
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)
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,18 @@
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
def register(uris: str,
notification: str=undefined):
notification: str=None):
""" Decorator to register function to the dispatcher
"""
if not isinstance(uris, list):
@ -29,6 +32,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 +50,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()
config = await Config(self.option)
await config.property.read_write()
# set message to the uri name
config.option('message').value.set(message)
await config.option('message').value.set(message)
# get message argument
subconfig = config.option(message)
return set(config.option(message).value.dict().keys())
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 +92,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()
config = await Config(self.option)
await config.property.read_write()
# set message to the message name
config.option('message').value.set(message)
await config.option('message').value.set(message)
# get message argument
subconfig = config.option(message)
return set(config.option(message).value.dict().keys())
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 +149,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 +165,6 @@ class RegisterDispatcher:
module_name,
function,
function_args,
inject_risotto_context,
notification)
def register_rpc(self,
@ -188,12 +173,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 +186,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,
'functions': function,
'arguments': function_args,
'risotto_context': inject_risotto_context}
if notification:
'function': function,
'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 +212,62 @@ 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
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().get('database')
self.pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user'))
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

@ -4,15 +4,17 @@ from importlib import import_module
from ..dispatcher import dispatcher
def list_import():
def load_services(modules=None,
validate: bool=True,
test: bool=False):
abs_here = dirname(abspath(__file__))
here = basename(abs_here)
module = basename(dirname(abs_here))
for filename in listdir(abs_here):
if not modules:
modules = listdir(abs_here)
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.validate()
list_import()
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,121 @@
from os import listdir
from os.path import join
from traceback import print_exc
from yaml import load, SafeLoader
from typing import Dict, List
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')
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, ApplicationServiceDependencies, ApplicationServiceReleaseId) VALUES ($1,$2,$3,$4)
RETURNING ApplicationServiceId
"""
applicationservice_id = await risotto_context.connection.fetchval(applicationservice_update_query,
applicationservice_name,
applicationservice_description,
applicationservice_dependencies,
release_id)
return {'applicationservice_name': applicationservice_name,
'applicationservice_description': applicationservice_description,
'applicationservice_release_id': release_id,
'applicationservice_id': applicationservice_id}
@register('v1.applicationservice.create')
async def applicationservice_create(self,
risotto_context: Context,
applicationservice_name: str,
applicationservice_description: str,
applicationservice_dependencies: List[int],
release_id: int) -> Dict:
return await self._applicationservice_create(risotto_context,
applicationservice_name,
applicationservice_description,
applicationservice_dependencies,
release_id)
@register('v1.applicationservice.dataset.updated')
async def applicationservice_update(self,
risotto_context: Context,
source_name: str,
release_distribution: str) -> Dict:
source = await self.call('v1.source.describe',
risotto_context,
source_name=source_name)
release = await self.call('v1.source.release.get_by_distribution',
risotto_context,
source_id=source['source_id'],
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'],
[], # FIXME dependencies
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:
applicationservice_query = """
SELECT ApplicationServiceId as applicationservice_id, ApplicationServiceName as applicationservice_name, ApplicationServiceDependencies as applicationservice_dependencies, 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}'))
return dict(applicationservice)
@register('v1.applicationservice.describe')
async def applicationservice_describe(self,
risotto_context: Context,
applicationservice_name,
release_id):
applicationservice_query = """
SELECT ApplicationServiceId as applicationservice_id, ApplicationServiceName as applicationservice_name, ApplicationServiceDependencies as applicationservice_dependencies, 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)

View File

@ -1,16 +1,16 @@
from lxml.etree import parse
from io import BytesIO
from os import unlink
from os.path import isdir, isfile, join
from traceback import print_exc
from json import dumps
from typing import Dict
from typing import Dict, List
from tiramisu import Storage, delete_session, MetaConfig, MixConfig
from rougail import load as rougail_load
from ...controller import Controller
from ...register import register
from ...config import ROOT_CACHE_DIR, DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH
from ...config import DATABASE_DIR, ROUGAIL_DTD_PATH, get_config
from ...context import Context
from ...utils import _
from ...error import CallError, RegistrationError
@ -18,15 +18,19 @@ from ...logger import log
class Risotto(Controller):
servermodel = {}
server = {}
def __init__(self) -> None:
for dirname in [ROOT_CACHE_DIR, DATABASE_DIR, ROUGAIL_DTD_PATH]:
def __init__(self,
test) -> None:
global conf_storage
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
for dirname in [self.cache_root_path, 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__()
if not test:
self.save_storage = Storage(engine='sqlite3', dir_database=DATABASE_DIR)
self.servermodel = {}
self.server = {}
super().__init__(test)
async def on_join(self,
risotto_context: Context) -> None:
@ -39,9 +43,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,383 +53,389 @@ 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(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,
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,
persistent=True,
session_id=session_id,
storage=self.save_storage)
mixconfig = await MixConfig(children=[],
optiondescription=optiondescription,
persistent=True,
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,
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,
persistent=True)
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,
server_id: int) -> None:
# delete config to it's parents
for config in self.server[server_id].values():
for parent in config.config.parents():
parent.config.pop(config.config.name())
delete_session(config.config.name())
for server_type in ['server', 'server_to_deploy']:
config = self.server[server_id]['server']
for parent in await config.config.parents():
await parent.config.pop(await config.config.name())
delete_session(storage=self.save_storage,
session_id=await config.config.name())
# delete metaconfig
del self.server[server_id]
@register('v1.servermodel.created')
async def servermodel_created(self,
servermodels) -> None:
risotto_context: Context,
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
""" when servermodels are created, load it and do link
"""
for servermodel in servermodels:
await self.load_servermodel(servermodel['servermodelid'], servermodel['servermodelname'])
for servermodel in servermodels:
if 'servermodelparentsid' in servermodel:
for servermodelparentid in servermodel['servermodelparentsid']:
self.servermodel_legacy(servermodel['servermodelname'], servermodel['servermodelid'], servermodelparentid)
await self.load_and_link_servermodel(risotto_context,
servermodel_id,
servermodel_name,
servermodel_parents_id)
@register('v1.servermodel.updated')
async def servermodel_updated(self,
risotto_context: Context,
servermodels) -> None:
for servermodel in servermodels:
servermodelid = servermodel['servermodelid']
servermodelname = servermodel['servermodelname']
servermodelparentsid = servermodel.get('servermodelparentsid')
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)
# get current servermodel
old_servermodel = self.servermodel[servermodelid]
async def load_and_link_servermodel(self,
risotto_context: Context,
servermodel_id: int,
servermodel_name: str,
servermodel_parents_id: List[int]) -> None:
await self.load_servermodel(risotto_context,
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)
# create new one
await self.load_servermodel(servermodelid, servermodelname)
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 await mixconfig.config.list():
children.append(child)
await mixconfig.config.pop(await child.config.name())
await metaconfig.config.pop(await mixconfig.config.name())
delete_session(storage=self.save_storage,
session_id=await mixconfig.config.name())
del mixconfig
for parent in await metaconfig.config.parents():
await parent.config.pop(await metaconfig.config.name())
delete_session(storage=self.save_storage,
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)
# migrate all informations
self.servermodel[servermodelid].value.importation(old_servermodel.value.exportation())
self.servermodel[servermodelid].permissive.importation(old_servermodel.permissive.exportation())
self.servermodel[servermodelid].property.importation(old_servermodel.property.exportation())
# remove link to legacy
if servermodelparentsid:
for servermodelparentid in servermodelparentsid:
mix = self.servermodel[servermodelparentid].config.get('m_v_' + str(servermodelparentid))
try:
mix.config.pop(old_servermodel.config.name())
except:
# if mix config is reloaded too
pass
# add new link
self.servermodel_legacy(servermodelname, servermodelid, servermodelparentid)
# reload servers or servermodels in servermodel
for subconfig in old_servermodel.config.list():
if not isinstance(subconfig, MixConfig):
# a server
name = subconfig.config.name()
if name.startswith('str_'):
continue
server_id = subconfig.information.get('server_id')
server_name = subconfig.information.get('server_name')
try:
old_servermodel.config.pop(name)
old_servermodel.config.pop(f'std_{server_id}')
except:
pass
del self.server[server_id]
self.load_server(risotto_context,
server_id,
server_name,
servermodelid)
else:
# a servermodel
for subsubconfig in subconfig.config.list():
name = subsubconfig.config.name()
try:
subconfig.config.pop(name)
except:
pass
self.servermodel_legacy(subsubconfig.information.get('servermodel_name'),
subsubconfig.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,
deploy: bool) -> bytes:
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 deploy:
if deployed:
server = self.server[server_id]['server']
else:
server = self.server[server_id]['server_to_deploy']
server.property.read_only()
await server.property.read_only()
try:
dico = server.value.dict(fullpath=True)
configuration = await server.value.dict(fullpath=True,
leader_to_list=True)
except:
if deploy:
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 dumps(dico).encode()
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,
'deploy': True}
'server_name': server_name,
'deployed': True}

View File

@ -1,8 +1,65 @@
from typing import Dict
from ...controller import Controller
from ...register import register
from ...context import Context
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}]
@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,
source_name: str,
release_distribution: str) -> Dict:
servermodel = await self.call('v1.servermodel.describe',
risotto_context,
servermodel_name=servermodel_name,
source_name=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

@ -1,65 +1,424 @@
from shutil import rmtree, copyfile
from os import listdir, makedirs
from os.path import join, isdir, isfile
from yaml import load, SafeLoader
from traceback import print_exc
from typing import Dict, List, Optional
from rougail import CreoleObjSpace
from rougail.config import dtdfilename
from ...controller import Controller
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'}]
def __init__(self,
test: bool) -> None:
self.source_root_path = get_config().get('source').get('root_path')
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
if not isdir(self.cache_root_path):
makedirs(join(self.cache_root_path))
@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': ''}
async def on_join(self,
risotto_context: Context) -> None:
internal_source = await self.call('v1.source.create',
risotto_context,
source_name='internal',
source_url='none')
internal_release = await self.call('v1.source.release.create',
risotto_context,
source_name='internal',
release_name='none',
release_distribution='last')
self.internal_release_id = internal_release['release_id']
async def servermodel_gen_funcs(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
release_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 applicationservice_id, applicationservice_infos in dependencies.items():
applicationservice_name, as_release_id = applicationservice_infos
path = join(self.source_root_path,
release_cache[as_release_id]['source_name'],
release_cache[as_release_id]['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}"'))
eolobj = CreoleObjSpace(dtdfilename)
async def servermodel_gen_schema(self,
servermodel_name: str,
servermodel_id: int,
dependencies: Dict,
release_cache: Dict,
risotto_context: Context) -> None:
paths = []
extras = []
as_names = set()
for applicationservice_id, applicationservice_infos in dependencies.items():
applicationservice_name, as_release_id = applicationservice_infos
# load creole dictionaries
path = join(self.source_root_path,
release_cache[as_release_id]['source_name'],
release_cache[as_release_id]['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,
release_cache[as_release_id]['source_name'],
release_cache[as_release_id]['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,
release_cache: Dict,
risotto_context: Context) -> None:
as_names = []
dest_dir = self.get_servermodel_cache(servermodel_id, 'templates')
makedirs(dest_dir)
for applicationservice_id, applicationservice_infos in dependencies.items():
applicationservice_name, as_release_id = applicationservice_infos
path = join(self.source_root_path,
release_cache[as_release_id]['source_name'],
release_cache[as_release_id]['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}"'))
async def _servermodel_create(self,
risotto_context: Context,
servermodel_name: str,
servermodel_description: str,
servermodel_parents_id: List[int],
dependencies: List[int],
release_id: int,
release_cache: Dict=None) -> Dict:
servermodel_update = """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}'
applicationservice = await self.call('v1.applicationservice.create',
risotto_context,
applicationservice_name=as_name,
applicationservice_description=as_description,
applicationservice_dependencies=dependencies,
release_id=self.internal_release_id)
applicationservice_id = applicationservice['applicationservice_id']
servermodel_id = await risotto_context.connection.fetchval(servermodel_update,
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)
# get all dependencies for this application service
dependencies = await self.get_applicationservices(risotto_context,
applicationservice_id)
# build cache to have all release informations
if release_cache is None:
release_cache = {}
for applicationservice_id, applicationservice_infos in dependencies.items():
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.servermodel_gen_funcs(servermodel_name,
servermodel_id,
dependencies,
release_cache,
risotto_context)
await self.servermodel_gen_schema(servermodel_name,
servermodel_id,
dependencies,
release_cache,
risotto_context)
await self.servermodel_copy_templates(servermodel_name,
servermodel_id,
dependencies,
release_cache,
risotto_context)
sm_dict = {'servermodel_name': servermodel_name,
'servermodel_description': servermodel_description,
'servermodel_parents_id': servermodel_parents_id,
'release_id': release_id,
'servermodel_id': servermodel_id}
await self.publish('v1.servermodel.created',
risotto_context,
**sm_dict)
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
async def get_servermodel_id_by_name(self,
risotto_context: Context,
servermodel_name: str,
release_id: int):
sql = 'SELECT ServermodelId as servermodel_id FROM Servermodel WHERE ServermodelName = $1 AND ServermodelReleaseId = $2',
return await risotto_context.connection.fetchval(sql,
servermodel_name,
release_id)['servermodel_id']
@register('v1.servermodel.dataset.updated')
async def servermodel_update(self,
risotto_context: Context,
source_name: str,
release_distribution: int):
source = await self.call('v1.source.describe',
risotto_context,
source_name=source_name)
release = await self.call('v1.source.release.get_by_distribution',
risotto_context,
source_id=source['source_id'],
release_distribution=release_distribution)
release_id = release['release_id']
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
release_cache = {release['release_id']: release}
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()
servermodelparent_id = []
for new_servermodel in parents:
if not servermodels[new_servermodel]['done']:
servermodel_description = servermodels[new_servermodel]
parent = servermodel_description['parent']
if not servermodelparent_id and parent is not None:
# parent is a str, so get ID
servermodelparent_id = [await self.get_servermodel_id_by_name(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,
release_id=release_id)
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,
servermodelparent_id,
dependencies,
release_id,
release_cache)
servermodel_id = servermodel_ob['servermodel_id']
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}"))
servermodelparent_id = [servermodel_id]
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
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)
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id
FROM Servermodel
WHERE ServermodelName=$1 AND ServermodelReleaseId=$2
'''
servermodel = await risotto_context.connection.fetchrow(sql,
servermodel_name,
release['release_id'])
if not servermodel:
raise Exception(_(f'{servermodel_id} is not a valid ID for a servermodel'))
return dict(servermodel)
@register('v1.servermodel.get_by_id')
async def servermodel_get_by_id(self,
risotto_context: Context,
servermodel_id: int) -> Dict:
sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id
FROM Servermodel
WHERE ServermodelId=$1
'''
servermodel = await risotto_context.connection.fetchrow(sql,
servermodel_id)
if not servermodel:
raise Exception(_(f'{servermodel_id} is not a valid ID for a servermodel'))
return dict(servermodel)
async def _parse_depends(self,
risotto_context: Context,
applicationservice_id: int,
or_depends: list,
ids: list) -> None:
applicationservice = await self.call('v1.applicationservice.get_by_id',
risotto_context,
applicationservice_id=applicationservice_id)
ids[applicationservice_id] = (applicationservice['applicationservice_name'],
applicationservice['applicationservice_release_id'])
for depend in applicationservice['applicationservice_dependencies']:
if isinstance(depend, dict):
or_depends.append(depend['or'])
elif depend not in ids:
await self._parse_depends(risotto_context,
depend,
or_depends,
ids)
async def _parse_or_depends(self,
risotto_context: Context,
or_depends: list,
ids: list) -> None:
new_or_depends = []
set_ids = set(ids)
for or_depend in or_depends:
if not set(or_depend) & set_ids:
applicationservice_id= or_depend[0]
await self._parse_depends(risotto_context,
applicationservice_id,
new_or_depends,
ids)
if new_or_depends:
await self._parse_or_depends(risotto_context,
new_or_depends,
ids)
async def get_applicationservices(self,
risotto_context: Context,
applicationservice_id: int) -> list:
"""Return consolidated dependencies or raise.
"""
or_depends = []
ids = {}
await self._parse_depends(risotto_context,
applicationservice_id,
or_depends,
ids)
await self._parse_or_depends(risotto_context,
or_depends,
ids)
return ids

View File

@ -1,16 +1,13 @@
from os import urandom # , unlink
from binascii import hexlify
from traceback import print_exc
from json import dumps
from typing import Dict, List, Optional, Any
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
@ -18,7 +15,8 @@ from ...dispatcher import dispatcher
class Risotto(Controller):
def __init__(self):
def __init__(self,
test):
self.modify_storage = Storage(engine='dictionary')
def get_storage(self,
@ -28,6 +26,7 @@ class Risotto(Controller):
return storage_servermodel
def get_session(self,
risotto_context: Context,
session_id: str,
type: str) -> Dict:
""" Get session information from storage
@ -36,7 +35,8 @@ class Risotto(Controller):
storage = storage_server
else:
storage = storage_servermodel
return storage.get_session(session_id)
return storage.get_session(session_id,
risotto_context.username)
def get_session_informations(self,
risotto_context: Context,
@ -44,9 +44,9 @@ class Risotto(Controller):
type: str) -> Dict:
""" format session with a session ID name
"""
session = self.get_session(session_id,
type,
risotto_context.username)
session = self.get_session(risotto_context,
session_id,
type)
return self.format_session(session_id,
session)
@ -63,60 +63,107 @@ 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(session_id,
type)
return self.get_session_informations(risotto_context,
session_id,
'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,
source_name: str,
release_distribution: 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=source_name,
release_distribution=release_distribution)
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]
storage = self.get_storage(type,
risotto_context.username)
return [self.format_session(session_id, session) or session_id, session in storage.get_sessions().items()]
storage = self.get_storage(type)
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,
@ -127,22 +174,23 @@ class Risotto(Controller):
storage = self.get_storage(type)
# to validate the session right
storage.get_session(session_id,
username)
risotto_context.username)
if namespace is not None:
storage.set_namespace(session_id,
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)
return self.get_session_informations(session_id,
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,
@ -152,69 +200,81 @@ class Risotto(Controller):
value: Any,
value_multi: Optional[List]) -> Dict:
type = risotto_context.message.rsplit('.', 2)[-2]
session = self.get_session(session_id,
type,
risotto_context.username)
session = self.get_session(risotto_context,
session_id,
type)
# if multi and not follower the value is in fact in value_multi
# FIXME option = session['option'].option(name).option
option = session['config'].option(name).option
if option.ismulti() and not option.isfollower():
if await option.ismulti() and not await option.isfollower():
value = value_multi
try:
update = {'name': name,
'action': action,
'value': value}
if index is not None:
update['index'] = index
updates = {'updates': [update]}
session['option'].updates(updates)
except Exception as err:
if DEBUG:
print_exc()
raise CallError(str(err))
#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 = await session['option'].updates(updates)
if update['name'] in ret:
for val in ret[update['name']][index]:
if isinstance(val, ValueError):
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:
type = risotto_context.message.rsplit('.', 2)[-2]
session = self.get_session(session_id,
type,
risotto_context.username)
session = self.get_session(risotto_context,
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) -> Dict:
session_id: str,
name: Optional[str]) -> Dict:
type = risotto_context.message.rsplit('.', 2)[-2]
session = self.get_session(session_id,
type,
risotto_context.username)
session = self.get_session(risotto_context,
session_id,
type)
info = self.format_session(session_id, session)
info['content'] = dumps(session['option'].value.dict(fullpath=True))
if name is not None:
content = {name: await session['config'].option(name).value.get()}
else:
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,
@ -226,13 +286,13 @@ 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())
await config.value.importation(await modif_config.value.exportation())
await config.permissive.importation(await modif_config.permissive.exportation())
storage.del_session(session_id)
return self.format_session(session_id, session)
@ -243,7 +303,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,
@ -252,4 +312,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.session[id]['config']
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.session[id_]['config']
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,
@ -94,13 +94,14 @@ class Storage(object):
return self.sessions;
def get_session(self,
id: int,
session_id: int,
username: str) -> Dict:
if id not in self.sessions:
raise Exception(f'the session {id} not exists')
if username != storage.get_session(session_id)['username']:
if session_id not in self.sessions:
raise Exception(f'the session "{session_id}" not exists')
session = self.sessions[session_id]
if username != session['username']:
raise NotAllowedError()
return self.sessions[id]
return session
def del_session(self,
id: int):
@ -112,10 +113,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):
@ -123,10 +124,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,165 @@
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_list(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)
@register('v1.source.release.get_by_distribution')
async def release_get_by_distribution(self,
risotto_context: Context,
source_id: int,
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 Release.ReleaseSourceId = $1 AND Release.ReleaseDistribution = $2 AND Source.SourceId = Release.ReleaseSourceId"""
result = await risotto_context.connection.fetchrow(release_query,
source_id,
release_distribution)
if not result:
raise Exception(_(f'unknown distribution {release_distribution} with source {source_id}'))
return dict(result)

View File

@ -1,34 +1,46 @@
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:
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
server_id = server['server_id']
servermodel_id = server['server_servermodel_id']
config_module = dispatcher.get_service('config')
server = config_module.server[server_id]
config = meta = server['server'].config.deepcopy(storage=self.storage)
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'))
config = meta = await server['server'].config.deepcopy(storage=self.storage)
while True:
try:
children = list(config.config.list())
children = list(await 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):
@ -38,12 +50,12 @@ class Risotto(Controller):
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)
templates_dir = join(self.cache_root_path, str(servermodel_id), 'templates')
await generate(config,
server['funcs_file'],
templates_dir,
tmp_dir,
configurations_dir)
return {'server_id': server_id,
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.server.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.applicationservice.get_by_id',
'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.get_by_distribution',
'v1.source.release.get_by_id',
'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.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

View File

@ -0,0 +1,211 @@
from typing import Dict, Optional
from ...controller import Controller
from ...register import register
from ...context import Context
from ...utils import _
from ...config import get_config
class Risotto(Controller):
async def on_join(self,
risotto_context: Context) -> None:
""" pre-load servermodel and server
"""
user_login = get_config()['global']['admin_user']
sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
if await risotto_context.connection.fetchval(sql,
user_login) is None:
await self._user_create(risotto_context,
user_login,
user_login,
user_login)
await self._user_role_create(risotto_context,
user_login,
'administrator',
None,
None)
async def _user_create(self,
risotto_context: Context,
user_login: str,
user_name: str,
user_surname: str) -> Dict:
user_insert = """INSERT INTO RisottoUser(UserLogin, UserName, UserSurname)
VALUES ($1,$2,$3)
RETURNING UserId
"""
user_id = await risotto_context.connection.fetchval(user_insert,
user_login,
user_name,
user_surname)
await self.call('v1.user.role.create',
risotto_context,
user_login=user_login,
role_name='all')
return {'user_id': user_id,
'user_login': user_login,
'user_name': user_name,
'user_surname': user_surname}
@register('v1.user.create')
async def user_create(self,
risotto_context: Context,
user_login: str,
user_name: str,
user_surname: str) -> Dict:
return await self._user_create(risotto_context,
user_login,
user_name,
user_surname)
@register('v1.user.list')
async def user_list(self,
risotto_context: Context) -> Dict:
sql = '''
SELECT UserId as user_id, UserLogin as user_login, UserName as user_name, UserSurname as user_surname
FROM RisottoUser
'''
users = await risotto_context.connection.fetch(sql)
return [dict(r) for r in users]
@register('v1.user.delete')
async def user_delete(self,
risotto_context: Context,
user_login: str) -> Dict:
sql = '''
DELETE FROM RisottoUser
WHERE UserLogin = $1
RETURNING UserId as user_id, UserLogin as user_login, UserName as user_name, UserSurname as user_surname
'''
user = await risotto_context.connection.fetchrow(sql,
user_login)
if user is None:
raise Exception(_(f'unable to find user {user_login}'))
return dict(user)
async def _user_role_create(self,
risotto_context: Context,
user_login: str,
role_name: str,
role_attribute: str,
role_attribute_value: str) -> Dict:
# Verify if user exists and get ID
sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
user_id = await risotto_context.connection.fetchval(sql,
user_login)
if user_id is None:
raise Exception(_(f'unable to find user {user_login}'))
if role_attribute == role_attribute_value == None:
sql = '''SELECT RoleId
FROM UserRole
WHERE RoleUserId = $1 AND RoleName = $2
'''
role_id = await risotto_context.connection.fetchval(sql,
user_id,
role_name)
else:
sql = '''SELECT RoleId
FROM UserRole
WHERE RoleUserId = $1 AND RoleName = $2 AND RoleAttribute = $3 AND RoleAttributeValue = $4
'''
role_id = await risotto_context.connection.fetchval(sql,
user_id,
role_name,
role_attribute,
role_attribute_value)
if role_id is None:
sql = '''INSERT INTO UserRole(RoleUserId, RoleName, RoleAttribute, RoleAttributeValue)
VALUES($1,$2,$3,$4)
RETURNING RoleId
'''
role_id = await risotto_context.connection.fetchval(sql,
user_id,
role_name,
role_attribute,
role_attribute_value)
return {'role_id': role_id,
'user_login': user_login,
'role_name': role_name,
'role_attribute': role_attribute,
'role_attribute_value': role_attribute_value}
@register('v1.user.role.create')
async def user_role_create(self,
risotto_context: Context,
user_login: str,
role_name: str,
role_attribute: str,
role_attribute_value: str) -> Dict:
return await self._user_role_create(risotto_context,
user_login,
role_name,
role_attribute,
role_attribute_value)
@register('v1.user.role.list')
async def user_role_list(self,
risotto_context: Context,
user_login: Optional[str]) -> Dict:
if not user_login:
sql = '''
SELECT RoleId as role_id, RoleName as role_name, RoleAttribute as role_attribute, RoleAttributeValue as role_attribute_value, RisottoUser.UserLogin as user_login
FROM UserRole, RisottoUser
WHERE UserRole.RoleUserId = RisottoUser.UserId
'''
roles = await risotto_context.connection.fetch(sql)
else:
# Verify if user exists and get ID
sql = '''
SELECT UserId
FROM RisottoUser
WHERE UserLogin = $1
'''
user_id = await risotto_context.connection.fetchval(sql,
user_login)
if user_id is None:
raise Exception(_(f'unable to find user {user_login}'))
sql = '''
SELECT RoleId as role_id, RoleName as role_name, RoleAttribute as role_attribute, RoleAttributeValue as role_attribute_value, RisottoUser.UserLogin as user_login
FROM UserRole, RisottoUser
WHERE UserRole.RoleUserId = RisottoUser.UserId AND UserRole.RoleUserId = $1
'''
roles = await risotto_context.connection.fetch(sql,
user_id)
return [dict(r) for r in roles]
#
# FIXME comment savoir quel role il faut supprimer ? avec attribut ou juste l'ID ?
# @register('v1.user.role.delete')
# async def user_role_delete(self,
# risotto_context: Context,
# user_login: str,
# role_name: str) -> Dict:
# # Verify if user exists and get ID
# sql = '''
# SELECT UserId
# FROM RisottoUser
# WHERE UserLogin = $1
# '''
# user_id = await risotto_context.connection.fetchval(sql,
# user_login)
# if user_id is None:
# raise Exception(_(f'unable to find user {user_login}'))
# sql = '''
# DELETE FROM RisottoRole
# WHERE RoleName = $1 AND UserId = $2
# RETURNING RoleId as role_id, RoleName as role_name, RoleAttribute as role_attribute, RoleAttributeValue as role_attribute_value
# '''
# role = await risotto_context.connection.fetchrow(sql,
# role_name,
# user_id)
# if role is None:
# raise Exception(_(f'unable to find role {role_name}'))
# return dict(role)

View File

@ -0,0 +1,50 @@
<?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>

0
tests/data/1/funcs.py Normal file
View File

View File

@ -0,0 +1,50 @@
<?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>

0
tests/data/2/funcs.py Normal file
View File

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