Compare commits

..

13 Commits

81 changed files with 1492 additions and 560 deletions

View File

@ -20,8 +20,8 @@ docker run -d --add-host reload.example.com:127.0.0.1 -p 80:80 coudot/lemonldap-
Démarrer un serveur postgresql de test Démarrer un serveur postgresql de test
``` ```
podman pull docker.io/library/postgres:11-alpine docker run -dt -p 5432:5432 --name postgres postgres:11-alpine
podman run -dt -p 5432:5432 postgres:11-alpine docker exec -ti postgres bash
psql -U postgres -h localhost -c "CREATE ROLE risotto WITH LOGIN PASSWORD 'risotto';" 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 "CREATE DATABASE risotto;"
@ -35,3 +35,38 @@ Gestion de la base de données avec Sqitch
cpanm --quiet --notest App::Sqitch cpanm --quiet --notest App::Sqitch
sqitch init risotto --uri https://forge.cadoles.com/Infra/risotto --engine pg 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

29
docker/Dockerfile Normal file
View File

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

14
docker/README.md Normal file
View File

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

View File

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

View File

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

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
applicationservice_name: applicationservice_name:
type: String type: String

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_name: source_name:
type: String type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
applicationservice_name: applicationservice_name:
type: String type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
applicationservice_id: applicationservice_id:
type: Number type: Number

View File

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

View File

@ -6,14 +6,11 @@ description: |
pattern: rpc pattern: rpc
public: false
parameters: parameters:
server_id: server_name:
type: Number type: String
ref: Server.ServerId ref: Server.ServerName
description: | description: Nom du serveur.
Identifiant du serveur.
deployed: deployed:
type: Boolean type: Boolean
description: Configuration de type déployée. description: Configuration de type déployée.

View File

@ -6,13 +6,16 @@ description: |
pattern: event pattern: event
public: false
parameters: parameters:
server_id: server_id:
type: Number type: Number
description: | description: |
Identifiant du serveur. Identifiant du serveur.
server_name:
type: String
ref: Server.ServerName
shortarg: s
description: Nom du serveur.
deployed: deployed:
type: Boolean type: Boolean
description: Configuration de type déployée. description: Configuration de type déployée.

View File

@ -1,32 +0,0 @@
---
uri: servermodel.describe
description: |
Retourne les attributs détaillés d'un modèle de serveur.
pattern: rpc
public: true
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.
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

@ -5,8 +5,6 @@ description: Crée un serveur.
pattern: rpc pattern: rpc
public: true
parameters: parameters:
server_name: server_name:
type: String type: String
@ -16,11 +14,21 @@ parameters:
type: String type: String
shortarg: d shortarg: d
description: Description du serveur. description: Description du serveur.
server_servermodel_id: servermodel_name:
type: Number type: String
shortarg: m shortarg: m
ref: Servermodel.ServermodelId ref: Servermodel.ServermodelName
description: Identifiant du modèle de serveur. 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: response:
type: Server type: Server

View File

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

View File

@ -6,8 +6,6 @@ description: |
pattern: event pattern: event
public: false
parameters: parameters:
server_id: server_id:
type: Number type: Number

View File

@ -5,11 +5,10 @@ description: Retourne les attributs détaillés dun serveur.
pattern: rpc pattern: rpc
public: true
parameters: parameters:
server_name: server_name:
type: String type: String
shortarg: s
ref: Server.ServerName ref: Server.ServerName
description: Nom du serveur. description: Nom du serveur.

View File

@ -4,8 +4,6 @@ uri: server.list
description: | description: |
Liste les serveurs disponibles. Liste les serveurs disponibles.
public: true
pattern: rpc pattern: rpc
response: response:

View File

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

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_name: source_name:
type: String type: String

View File

@ -0,0 +1,28 @@
---
uri: servermodel.describe
description: |
Retourne les attributs détaillés d'un modèle de serveur.
pattern: rpc
parameters:
servermodel_name:
type: String
shortarg: s
description: Identifiant du modèle de serveur à récupérer.
ref: Servermodel.ServermodelId
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: Servermodel
description: Description du modèle de serveur.

View File

@ -5,8 +5,6 @@ description: Retourne les attributs détaillés d'un modèle de serveur suivant
pattern: rpc pattern: rpc
public: false
parameters: parameters:
servermodel_id: servermodel_id:
type: Number type: Number

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_id: source_id:
type: Number type: Number
@ -19,13 +17,3 @@ parameters:
response: response:
type: '[]Servermodel' type: '[]Servermodel'
description: Liste des modèles de serveur disponibles. 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,8 +5,6 @@ description: Des modèles de serveur ont été modifiés.
pattern: event pattern: event
public: false
parameters: parameters:
type: 'Servermodel' type: 'Servermodel'
description: Informations sur les modèles de serveur modifiés. description: Informations sur les modèles de serveur modifiés.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,15 +5,22 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
id: servermodel_name:
type: Number type: String
ref: Servermodel.ServermodelId ref: Servermodel.ServermodelName
shortarg: c shortarg: s
description: | description: Nom du serveurmodel.
Identifiant de la configuration. 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: response:
type: Session type: Session

View File

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

View File

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

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_name: source_name:
type: String type: String

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_id: source_id:
type: Number type: Number

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_name: source_name:
type: String type: String

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
response: response:
type: '[]Source' type: '[]Source'
description: Liste des sources disponibles. description: Liste des sources disponibles.

View File

@ -6,13 +6,11 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_id: source_name:
type: Number type: String
shortarg: i shortarg: s
description: ID de la source. description: Nom de la source.
release_name: release_name:
type: String type: String
shortarg: n shortarg: n

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

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
source_id: source_id:
type: Number type: Number

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
release_id: release_id:
type: Number type: Number

View File

@ -6,7 +6,11 @@ description: |
pattern: rpc pattern: rpc
public: true parameters:
source_name:
type: String
shortarg: s
description: Nom de la source.
response: response:
type: '[]Release' type: '[]Release'

View File

@ -6,15 +6,12 @@ description: |
pattern: rpc pattern: rpc
public: true
parameters: parameters:
server_name: server_name:
type: String type: String
ref: Server.ServerName ref: Server.ServerName
shortarg: s shortarg: s
description: | description: Nom du serveur.
Nom du serveur.
response: response:
type: Template 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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

7
requirements.txt Normal file
View File

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

View File

@ -82,11 +82,30 @@ CREATE TABLE RoleURI (
PRIMARY KEY (RoleName, 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(): async def main():
db_conf = get_config().get('database') db_conf = get_config().get('database')
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user')) #asyncpg.connect('postgresql://postgres@localhost/test')
engine = db_conf.get('engine')
host = db_conf.get('host')
dbname = db_conf.get('dbname')
dbuser = db_conf.get('user')
dbpassword = db_conf.get('password')
dbport = db_conf.get('port')
cfg = "{}://{}:{}@{}:{}/{}".format(engine, dbuser, dbpassword, host, dbport, dbname)
pool = await asyncpg.create_pool(cfg)
async with pool.acquire() as connection: async with pool.acquire() as connection:
async with connection.transaction(): async with connection.transaction():
returns = await connection.execute(VERSION_INIT) returns = await connection.execute(VERSION_INIT)
@ -95,3 +114,4 @@ if __name__ == '__main__':
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(main()) loop.run_until_complete(main())
# asyncio.run(main()) # asyncio.run(main())

View File

@ -1,32 +1,34 @@
HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages' MESSAGE_ROOT_PATH = 'messages'
DEBUG = True DATABASE_DIR = '/var/cache/risotto/database'
DATABASE_DIR = 'database'
INTERNAL_USER = 'internal' INTERNAL_USER = 'internal'
CONFIGURATION_DIR = 'configurations' CONFIGURATION_DIR = 'configurations'
TEMPLATE_DIR = 'templates' TEMPLATE_DIR = 'templates'
TMP_DIR = 'tmp' TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd' ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
POSTGRESQL_ADDRESS = '192.168.56.106' DEFAULT_USER = 'Anonymous'
POSTGRESQL_PORT = 5432
import os import os
from pathlib import PurePosixPath from pathlib import PurePosixPath
CURRENT_PATH = PurePosixPath(__file__) CURRENT_PATH = PurePosixPath(__file__)
def get_config(): def get_config():
return {'database': {'host': 'localhost', return {'database': {'engine': 'postgres',
'host': 'postgres',
'port': 5432, 'port': 5432,
'dbname': 'risotto', 'dbname': 'risotto',
'user': 'risotto', 'user': 'risotto',
'password': 'risotto', 'password': 'risotto',
}, },
'http_server': {'port': 8080}, 'http_server': {'port': 8080,
#'default_user': "gnunux"},
'default_user': DEFAULT_USER},
'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages', 'global': {'message_root_path': CURRENT_PATH.parents[2] / 'messages',
'debug': DEBUG, 'debug': True,
'internal_user': 'internal', 'internal_user': 'internal',
'check_role': False, 'check_role': True,
'rougail_dtd_path': '../rougail/data/creole.dtd'}, 'rougail_dtd_path': '../rougail/data/creole.dtd',
'admin_user': DEFAULT_USER},
'source': {'root_path': '/srv/seed'}, 'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'} 'cache': {'root_path': '/var/cache/risotto'}
} }

View File

@ -1,13 +1,12 @@
from tiramisu import Config from tiramisu import Config
from traceback import print_exc from traceback import print_exc
from copy import copy from copy import copy
from typing import Dict, Callable from typing import Dict, Callable, List, Optional
from json import dumps, loads from json import dumps, loads
from .utils import _ from .utils import _
from .error import CallError, NotAllowedError from .error import CallError, NotAllowedError
from .logger import log from .logger import log
from .config import DEBUG
from .config import get_config from .config import get_config
from .context import Context from .context import Context
from . import register from . import register
@ -17,21 +16,22 @@ import asyncpg
class CallDispatcher: class CallDispatcher:
async def valid_call_returns(self, async def valid_call_returns(self,
risotto_context: Context, risotto_context: Context,
function,
returns: Dict, returns: Dict,
kwargs: Dict): kwargs: Dict):
response = self.messages[risotto_context.version][risotto_context.message]['response'] response = self.messages[risotto_context.version][risotto_context.message]['response']
module_name = risotto_context.function.__module__.split('.')[-2] module_name = function.__module__.split('.')[-2]
function_name = risotto_context.function.__name__ function_name = function.__name__
if response.impl_get_information('multi'): if response.impl_get_information('multi'):
if not isinstance(returns, list): if not isinstance(returns, list):
err = _(f'function {module_name}.{function_name} has to return a 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)) raise CallError(str(err))
else: else:
if not isinstance(returns, dict): if not isinstance(returns, dict):
log.error_msg(risotto_context, kwargs, returns) await log.error_msg(risotto_context, kwargs, returns)
err = _(f'function {module_name}.{function_name} has to return a dict') 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)) raise CallError(str(err))
returns = [returns] returns = [returns]
if response is None: if response is None:
@ -46,11 +46,11 @@ class CallDispatcher:
await config.option(key).value.set(value) await config.option(key).value.set(value)
except AttributeError: except AttributeError:
err = _(f'function {module_name}.{function_name} return the unknown parameter "{key}"') 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)) raise CallError(str(err))
except ValueError: except ValueError:
err = _(f'function {module_name}.{function_name} return the parameter "{key}" with an unvalid value "{value}"') 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)) raise CallError(str(err))
await config.property.read_only() await config.property.read_only()
mandatories = await config.value.mandatory() mandatories = await config.value.mandatory()
@ -61,7 +61,7 @@ class CallDispatcher:
await config.value.dict() await config.value.dict()
except Exception as err: except Exception as err:
err = _(f'function {module_name}.{function_name} return an invalid response {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)) raise CallError(str(err))
async def call(self, async def call(self,
@ -77,24 +77,18 @@ class CallDispatcher:
version, version,
message, message,
'rpc') 'rpc')
self.check_message_type(risotto_context, function_objs = [self.messages[version][message]]
kwargs)
try:
kw = await self.load_kwargs_to_config(risotto_context,
f'{version}.{message}',
kwargs,
check_role)
function_obj = self.messages[version][message]
risotto_context.function = function_obj['function']
if function_obj['risotto_context']:
kw['risotto_context'] = risotto_context
# do not start a new database connection # do not start a new database connection
if function_obj['database'] and hasattr(old_risotto_context, 'connection'): if hasattr(old_risotto_context, 'connection'):
risotto_context.connection = old_risotto_context.connection risotto_context.connection = old_risotto_context.connection
if function_obj['database'] and not hasattr(risotto_context, 'connection'): return await self.launch(version,
db_conf = get_config().get('database') message,
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user')) risotto_context,
async with pool.acquire() as connection: check_role,
kwargs,
function_objs)
else:
async with self.pool.acquire() as connection:
await connection.set_type_codec( await connection.set_type_codec(
'json', 'json',
encoder=dumps, encoder=dumps,
@ -103,39 +97,12 @@ class CallDispatcher:
) )
risotto_context.connection = connection risotto_context.connection = connection
async with connection.transaction(): async with connection.transaction():
returns = await risotto_context.function(self.injected_self[function_obj['module']], **kw) return await self.launch(version,
else: message,
returns = await risotto_context.function(self.injected_self[function_obj['module']], **kw)
except CallError as err:
raise err
except Exception as err:
if get_config().get('global').get('debug'):
print_exc()
log.error_msg(risotto_context,
kwargs,
err)
raise CallError(str(err))
# valid returns
await self.valid_call_returns(risotto_context,
returns,
kwargs)
# log the success
log.info_msg(risotto_context,
kwargs,
_(f'returns {returns}'))
# 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, risotto_context,
**ret) check_role,
return returns kwargs,
function_objs)
class PublishDispatcher: class PublishDispatcher:
@ -149,41 +116,18 @@ class PublishDispatcher:
version, version,
message, message,
'event') 'event')
self.check_message_type(risotto_context, function_objs = self.messages[version][message].get('functions', [])
kwargs) # do not start a new database connection
try: if hasattr(old_risotto_context, 'connection'):
config_arguments = await self.load_kwargs_to_config(risotto_context,
kwargs)
except CallError as err:
return
except Exception as err:
# if there is a problem with arguments, just send an error et do nothing
if DEBUG:
print_exc()
log.error_msg(risotto_context, kwargs, err)
return
# config is ok, so publish the message
for function_obj in self.messages[version][message].get('functions', []):
function = function_obj['function']
module_name = function.__module__.split('.')[-2]
function_name = function.__name__
info_msg = _(f'in module {module_name}.{function_name}')
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
if function_obj['database'] and hasattr(old_risotto_context, 'connection'):
risotto_context.connection = old_risotto_context.connection risotto_context.connection = old_risotto_context.connection
if function_obj['database'] and not hasattr(risotto_context, 'connection'): return await self.launch(version,
db_conf = get_config().get('database') message,
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user')) risotto_context,
async with pool.acquire() as connection: check_role,
kwargs,
function_objs)
else:
async with self.pool.acquire() as connection:
await connection.set_type_codec( await connection.set_type_codec(
'json', 'json',
encoder=dumps, encoder=dumps,
@ -192,23 +136,12 @@ class PublishDispatcher:
) )
risotto_context.connection = connection risotto_context.connection = connection
async with connection.transaction(): async with connection.transaction():
returns = await function(self.injected_self[function_obj['module']], **kw) return await self.launch(version,
else: message,
returns = 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)
continue
else:
log.info_msg(risotto_context, kwargs, info_msg)
# notification
if function_obj.get('notification'):
notif_version, notif_message = function_obj['notification'].split('.', 1)
await self.publish(notif_version,
notif_message,
risotto_context, risotto_context,
**returns) check_role,
kwargs,
function_objs)
class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher): class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher):
@ -233,12 +166,12 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
risotto_context.version = version risotto_context.version = version
return risotto_context return risotto_context
def check_message_type(self, async def check_message_type(self,
risotto_context: Context, risotto_context: Context,
kwargs: Dict): kwargs: Dict):
if self.messages[risotto_context.version][risotto_context.message]['pattern'] != risotto_context.type: 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') 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) raise CallError(msg)
async def load_kwargs_to_config(self, async def load_kwargs_to_config(self,
@ -259,7 +192,7 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
try: try:
await subconfig.option(key).value.set(value) await subconfig.option(key).value.set(value)
except AttributeError: except AttributeError:
if DEBUG: if get_config()['global']['debug']:
print_exc() print_exc()
raise ValueError(_(f'unknown parameter in "{uri}": "{key}"')) raise ValueError(_(f'unknown parameter in "{uri}": "{key}"'))
# check mandatories options # check mandatories options
@ -283,9 +216,7 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
config: Config, config: Config,
user_login: str, user_login: str,
uri: str) -> None: uri: str) -> None:
db_conf = get_config().get('database') async with self.pool.acquire() as connection:
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(): async with connection.transaction():
# Verify if user exists and get ID # Verify if user exists and get ID
sql = ''' sql = '''
@ -328,6 +259,88 @@ class Dispatcher(register.RegisterDispatcher, CallDispatcher, PublishDispatcher)
return return
raise NotAllowedError(_(f'You ({user_login}) don\'t have any authorisation to access to "{uri}"')) 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() dispatcher = Dispatcher()
register.dispatcher = dispatcher register.dispatcher = dispatcher

View File

@ -10,13 +10,14 @@ from .context import Context
from .error import CallError, NotAllowedError, RegistrationError from .error import CallError, NotAllowedError, RegistrationError
from .message import get_messages from .message import get_messages
from .logger import log from .logger import log
from .config import DEBUG, HTTP_PORT from .config import get_config
from .services import load_services from .services import load_services
def create_context(request): def create_context(request):
risotto_context = Context() 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 return risotto_context
@ -52,11 +53,11 @@ class extra_route_handler:
except CallError as err: except CallError as err:
raise HTTPBadRequest(reason=str(err)) raise HTTPBadRequest(reason=str(err))
except Exception as err: except Exception as err:
if DEBUG: if get_config()['global']['debug']:
print_exc() print_exc()
raise HTTPInternalServerError(reason=str(err)) raise HTTPInternalServerError(reason=str(err))
log.info_msg(kwargs['risotto_context'], # await log.info_msg(kwargs['risotto_context'],
dict(request.match_info)) # dict(request.match_info))
return Response(text=dumps(returns)) return Response(text=dumps(returns))
@ -80,7 +81,7 @@ async def handle(request):
except CallError as err: except CallError as err:
raise HTTPBadRequest(reason=str(err).replace('\n', ' ')) raise HTTPBadRequest(reason=str(err).replace('\n', ' '))
except Exception as err: except Exception as err:
if DEBUG: if get_config()['global']['debug']:
print_exc() print_exc()
raise HTTPInternalServerError(reason=str(err)) raise HTTPInternalServerError(reason=str(err))
return Response(text=dumps({'response': text})) return Response(text=dumps({'response': text}))
@ -89,8 +90,19 @@ async def handle(request):
async def api(request, risotto_context): async def api(request, risotto_context):
global tiramisu global tiramisu
if not tiramisu: if not tiramisu:
config = await Config(get_messages(load_shortarg=True, # check all URI that have an associated role
only_public=True)[1]) # 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() await config.property.read_write()
tiramisu = await config.option.dict(remotable='none') tiramisu = await config.option.dict(remotable='none')
return tiramisu return tiramisu
@ -113,11 +125,8 @@ async def get_app(loop):
print(_('======== Registered messages ========')) print(_('======== Registered messages ========'))
for message in messages: for message in messages:
web_message = f'/api/{version}/{message}' web_message = f'/api/{version}/{message}'
if dispatcher.messages[version][message]['public']:
print(f' - {web_message}')
else:
pattern = dispatcher.messages[version][message]['pattern'] pattern = dispatcher.messages[version][message]['pattern']
print(f' - {web_message} (private {pattern})') print(f' - {web_message} ({pattern})')
routes.append(post(web_message, handle)) routes.append(post(web_message, handle))
print() print()
print(_('======== Registered extra routes ========')) print(_('======== Registered extra routes ========'))
@ -133,7 +142,7 @@ async def get_app(loop):
del extra_routes del extra_routes
app.add_routes(routes) app.add_routes(routes)
await dispatcher.on_join() 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 tiramisu = None

View File

@ -1,13 +1,30 @@
from typing import Dict from typing import Dict, Any
from json import dumps
from .context import Context from .context import Context
from .utils import _ from .utils import _
from .config import DEBUG from .config import get_config
class Logger: class Logger:
""" An object to manager log """ 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, def _get_message_paths(self,
risotto_context: Context): risotto_context: Context):
paths = risotto_context.paths paths = risotto_context.paths
@ -20,10 +37,9 @@ class Logger:
else: else:
paths_msg += f'sub-messages: ' paths_msg += f'sub-messages: '
paths_msg += ' > '.join(paths) paths_msg += ' > '.join(paths)
paths_msg += ':'
return paths_msg return paths_msg
def error_msg(self, async def error_msg(self,
risotto_context: Context, risotto_context: Context,
arguments, arguments,
error: str, error: str,
@ -31,10 +47,14 @@ class Logger:
""" send message when an error append """ send message when an error append
""" """
paths_msg = self._get_message_paths(risotto_context) paths_msg = self._get_message_paths(risotto_context)
# if DEBUG:
print(_(f'{risotto_context.username}: ERROR: {error} ({paths_msg} with arguments "{arguments}": {msg})')) 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, async def info_msg(self,
risotto_context: Context, risotto_context: Context,
arguments: Dict, arguments: Dict,
msg: str=''): msg: str=''):
@ -44,19 +64,23 @@ class Logger:
paths_msg = self._get_message_paths(risotto_context) paths_msg = self._get_message_paths(risotto_context)
else: else:
paths_msg = '' paths_msg = ''
tmsg = _(f'{risotto_context.username}: INFO:{paths_msg}') if get_config()['global']['debug']:
if arguments: print(_(f'{risotto_context.username}: INFO:{paths_msg}'))
tmsg += _(f' with arguments "{arguments}"') await self.insert(msg,
if msg: paths_msg,
tmsg += f' {msg}' risotto_context,
'Info',
arguments)
if DEBUG: async def info(self,
print(tmsg) risotto_context,
def info(self,
msg): msg):
if DEBUG: if get_config()['global']['debug']:
print(msg) print(msg)
await self.insert(msg,
None,
risotto_context,
'Info')
log = Logger() log = Logger()

View File

@ -44,7 +44,6 @@ class MessageDefinition:
'uri', 'uri',
'description', 'description',
'parameters', 'parameters',
'public',
'errors', 'errors',
'pattern', 'pattern',
'related', 'related',
@ -54,7 +53,6 @@ class MessageDefinition:
# default value for non mandatory key # default value for non mandatory key
self.version = u'' self.version = u''
self.parameters = OrderedDict() self.parameters = OrderedDict()
self.public = False
self.errors = [] self.errors = []
self.related = [] self.related = []
self.response = None self.response = None
@ -63,10 +61,7 @@ class MessageDefinition:
for key, value in raw_def.items(): for key, value in raw_def.items():
if isinstance(value, str): if isinstance(value, str):
value = value.strip() value = value.strip()
if key == 'public': if key == 'pattern':
if not isinstance(value, bool):
raise ValueError(_("{} must be a boolean, not {}").format(key, value))
elif key == 'pattern':
if value not in ['rpc', 'event', 'error']: if value not in ['rpc', 'event', 'error']:
raise Exception(_('unknown pattern {}').format(value)) raise Exception(_('unknown pattern {}').format(value))
elif key == 'parameters': elif key == 'parameters':
@ -86,9 +81,6 @@ class MessageDefinition:
getattr(self, key) getattr(self, key)
except AttributeError: except AttributeError:
raise Exception(_('mandatory key not set {} message').format(key)) 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: if self.uri != message:
raise Exception(_(f'yaml file name "{message}.yml" does not match uri "{self.uri}"')) raise Exception(_(f'yaml file name "{message}.yml" does not match uri "{self.uri}"'))
@ -245,13 +237,16 @@ def split_message_uri(uri):
def get_message_file_path(version, message): def get_message_file_path(version, message):
return join(MESSAGE_ROOT_PATH, version, 'messages', message + '.yml') return join(MESSAGE_ROOT_PATH, version, 'messages', message + '.yml')
def list_messages(): def list_messages(uris):
messages = listdir(MESSAGE_ROOT_PATH) messages = listdir(MESSAGE_ROOT_PATH)
messages.sort() messages.sort()
for version in messages: for version in messages:
for message in listdir(join(MESSAGE_ROOT_PATH, version, 'messages')): for message in listdir(join(MESSAGE_ROOT_PATH, version, 'messages')):
if message.endswith('.yml'): 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: class CustomParam:
__slots__ = ('name', __slots__ = ('name',
@ -581,19 +576,19 @@ def _get_root_option(select_option, optiondescriptions):
return OptionDescription('root', 'root', options_obj) 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 """generate description from yml files
""" """
optiondescriptions = OrderedDict() optiondescriptions = OrderedDict()
optiondescriptions_name = [] optiondescriptions_name = []
optiondescriptions_info = {} optiondescriptions_info = {}
needs = OrderedDict() needs = OrderedDict()
messages = list(list_messages()) messages = list(list_messages(uris))
messages.sort() messages.sort()
for message_name in messages: for message_name in messages:
message_def = get_message(message_name) message_def = get_message(message_name)
if message_def.pattern not in ['rpc', 'event'] or \ if message_def.pattern not in ['rpc', 'event']:
(not message_def.public and only_public):
continue continue
optiondescriptions_name.append(message_def.uri) optiondescriptions_name.append(message_def.uri)
optiondescriptions_name.sort() optiondescriptions_name.sort()
@ -603,11 +598,9 @@ def get_messages(load_shortarg=False, only_public=False):
properties=frozenset(['mandatory', 'positional'])) properties=frozenset(['mandatory', 'positional']))
for message_name in messages: for message_name in messages:
message_def = get_message(message_name) message_def = get_message(message_name)
if message_def.pattern not in ['rpc', 'event'] or \ if message_def.pattern not in ['rpc', 'event']:
(not message_def.public and only_public):
continue continue
optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern, optiondescriptions_info[message_def.uri] = {'pattern': message_def.pattern}
'public': message_def.public}
if message_def.pattern == 'rpc': if message_def.pattern == 'rpc':
optiondescriptions_info[message_def.uri]['response'] = _parse_responses(message_def, optiondescriptions_info[message_def.uri]['response'] = _parse_responses(message_def,
message_name) message_name)

View File

@ -2,8 +2,9 @@ from tiramisu import Config
from inspect import signature from inspect import signature
from typing import Callable, Optional from typing import Callable, Optional
import asyncpg import asyncpg
from json import dumps, loads
from .utils import undefined, _ from .utils import _
from .error import RegistrationError from .error import RegistrationError
from .message import get_messages from .message import get_messages
from .context import Context from .context import Context
@ -11,8 +12,7 @@ from .config import INTERNAL_USER, get_config
def register(uris: str, def register(uris: str,
notification: str=undefined, notification: str=None):
database: bool=False):
""" Decorator to register function to the dispatcher """ Decorator to register function to the dispatcher
""" """
if not isinstance(uris, list): if not isinstance(uris, list):
@ -24,7 +24,6 @@ def register(uris: str,
dispatcher.set_function(version, dispatcher.set_function(version,
message, message,
notification, notification,
database,
function) function)
return decorator return decorator
@ -33,6 +32,8 @@ class RegisterDispatcher:
def __init__(self): def __init__(self):
# reference to instanciate module (to inject self in method): {"module_name": instance_of_module} # reference to instanciate module (to inject self in method): {"module_name": instance_of_module}
self.injected_self = {} self.injected_self = {}
# postgresql pool
self.pool = None
# list of uris with informations: {"v1": {"module_name.xxxxx": yyyyyy}} # list of uris with informations: {"v1": {"module_name.xxxxx": yyyyyy}}
self.messages = {} self.messages = {}
# load tiramisu objects # load tiramisu objects
@ -69,7 +70,6 @@ class RegisterDispatcher:
def get_function_args(): def get_function_args():
function_args = self.get_function_args(function) function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it # 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) return set(function_args)
@ -112,7 +112,6 @@ class RegisterDispatcher:
def get_function_args(): def get_function_args():
function_args = self.get_function_args(function) function_args = self.get_function_args(function)
# risotto_context is a special argument, remove it # 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) return set(function_args)
@ -133,7 +132,6 @@ class RegisterDispatcher:
version: str, version: str,
message: str, message: str,
notification: str, notification: str,
database: bool,
function: Callable): function: Callable):
""" register a function to an URI """ register a function to an URI
URI is a message URI is a message
@ -151,22 +149,12 @@ class RegisterDispatcher:
# True if first argument is the risotto_context # True if first argument is the risotto_context
function_args = self.get_function_args(function) function_args = self.get_function_args(function)
if function_args and function_args[0] == 'risotto_context':
inject_risotto_context = True
function_args.pop(0) function_args.pop(0)
else:
inject_risotto_context = False
# check if already register # check if already register
if 'function' in self.messages[version][message]: if 'function' in self.messages[version][message]:
raise RegistrationError(_(f'uri {version}.{message} already registered')) raise RegistrationError(_(f'uri {version}.{message} already registered'))
# check notification
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'))
# register # register
if self.messages[version][message]['pattern'] == 'rpc': if self.messages[version][message]['pattern'] == 'rpc':
register = self.register_rpc register = self.register_rpc
@ -177,8 +165,6 @@ class RegisterDispatcher:
module_name, module_name,
function, function,
function_args, function_args,
inject_risotto_context,
database,
notification) notification)
def register_rpc(self, def register_rpc(self,
@ -187,14 +173,10 @@ class RegisterDispatcher:
module_name: str, module_name: str,
function: Callable, function: Callable,
function_args: list, function_args: list,
inject_risotto_context: bool,
database: bool,
notification: Optional[str]): notification: Optional[str]):
self.messages[version][message]['module'] = module_name self.messages[version][message]['module'] = module_name
self.messages[version][message]['function'] = function self.messages[version][message]['function'] = function
self.messages[version][message]['arguments'] = function_args self.messages[version][message]['arguments'] = function_args
self.messages[version][message]['risotto_context'] = inject_risotto_context
self.messages[version][message]['database'] = database
if notification: if notification:
self.messages[version][message]['notification'] = notification self.messages[version][message]['notification'] = notification
@ -204,18 +186,14 @@ class RegisterDispatcher:
module_name: str, module_name: str,
function: Callable, function: Callable,
function_args: list, function_args: list,
inject_risotto_context: bool,
database: bool,
notification: Optional[str]): notification: Optional[str]):
if 'functions' not in self.messages[version][message]: if 'functions' not in self.messages[version][message]:
self.messages[version][message]['functions'] = [] self.messages[version][message]['functions'] = []
dico = {'module': module_name, dico = {'module': module_name,
'function': function, 'function': function,
'arguments': function_args, 'arguments': function_args}
'database': database, if notification and notification:
'risotto_context': inject_risotto_context}
if notification and notification is not undefined:
dico['notification'] = notification dico['notification'] = notification
self.messages[version][message]['functions'].append(dico) self.messages[version][message]['functions'].append(dico)
@ -242,11 +220,20 @@ class RegisterDispatcher:
raise RegistrationError(_(f'missing uri {missing_messages}')) raise RegistrationError(_(f'missing uri {missing_messages}'))
async def on_join(self): async def on_join(self):
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(): for module_name, module in self.injected_self.items():
risotto_context = Context() risotto_context = Context()
risotto_context.username = INTERNAL_USER risotto_context.username = INTERNAL_USER
risotto_context.paths.append(f'{module_name}.on_join') risotto_context.paths.append(f'{module_name}.on_join')
risotto_context.type = None risotto_context.type = None
risotto_context.connection = connection
await module.on_join(risotto_context) await module.on_join(risotto_context)
async def insert_message(self, async def insert_message(self,
@ -261,8 +248,16 @@ class RegisterDispatcher:
async def load(self): async def load(self):
# valid function's arguments # valid function's arguments
db_conf = get_config().get('database') 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: engine = db_conf.get('engine')
host = db_conf.get('host')
dbname = db_conf.get('dbname')
dbuser = db_conf.get('user')
dbpassword = db_conf.get('password')
dbport = db_conf.get('port')
cfg = "{}://{}:{}@{}:{}/{}".format(engine, dbuser, dbpassword, host, dbport, dbname)
self.pool = await asyncpg.create_pool(cfg)
async with self.pool.acquire() as connection:
async with connection.transaction(): async with connection.transaction():
for version, messages in self.messages.items(): for version, messages in self.messages.items():
for message, message_infos in messages.items(): for message, message_infos in messages.items():
@ -284,3 +279,4 @@ class RegisterDispatcher:
module_name) module_name)
await self.insert_message(connection, await self.insert_message(connection,
f'{version}.{message}') f'{version}.{message}')

View File

@ -35,7 +35,7 @@ class Risotto(Controller):
'applicationservice_release_id': release_id, 'applicationservice_release_id': release_id,
'applicationservice_id': applicationservice_id} 'applicationservice_id': applicationservice_id}
@register('v1.applicationservice.create', None, database=True) @register('v1.applicationservice.create')
async def applicationservice_create(self, async def applicationservice_create(self,
risotto_context: Context, risotto_context: Context,
applicationservice_name: str, applicationservice_name: str,
@ -48,7 +48,7 @@ class Risotto(Controller):
applicationservice_dependencies, applicationservice_dependencies,
release_id) release_id)
@register('v1.applicationservice.dataset.updated', None, database=True) @register('v1.applicationservice.dataset.updated')
async def applicationservice_update(self, async def applicationservice_update(self,
risotto_context: Context, risotto_context: Context,
source_name: str, source_name: str,
@ -90,7 +90,7 @@ class Risotto(Controller):
return {'retcode': 0, return {'retcode': 0,
'returns': _('Application Services successfully loaded')} 'returns': _('Application Services successfully loaded')}
@register('v1.applicationservice.get_by_id', None, database=True) @register('v1.applicationservice.get_by_id')
async def applicationservice_get_by_id(self, async def applicationservice_get_by_id(self,
risotto_context: Context, risotto_context: Context,
applicationservice_id: int) -> Dict: applicationservice_id: int) -> Dict:
@ -104,7 +104,7 @@ class Risotto(Controller):
raise Exception(_(f'unknown service with ID {applicationservice_id}')) raise Exception(_(f'unknown service with ID {applicationservice_id}'))
return dict(applicationservice) return dict(applicationservice)
@register('v1.applicationservice.describe', None, database=True) @register('v1.applicationservice.describe')
async def applicationservice_describe(self, async def applicationservice_describe(self,
risotto_context: Context, risotto_context: Context,
applicationservice_name, applicationservice_name,

View File

@ -10,7 +10,7 @@ from rougail import load as rougail_load
from ...controller import Controller from ...controller import Controller
from ...register import register from ...register import register
from ...config import DATABASE_DIR, DEBUG, ROUGAIL_DTD_PATH, get_config from ...config import DATABASE_DIR, ROUGAIL_DTD_PATH, get_config
from ...context import Context from ...context import Context
from ...utils import _ from ...utils import _
from ...error import CallError, RegistrationError from ...error import CallError, RegistrationError
@ -43,7 +43,7 @@ class Risotto(Controller):
risotto_context: Context) -> None: risotto_context: Context) -> None:
""" load all available servermodels """ load all available servermodels
""" """
log.info_msg(risotto_context, await log.info_msg(risotto_context,
None, None,
'Load servermodels') 'Load servermodels')
servermodels = await self.call('v1.servermodel.list', servermodels = await self.call('v1.servermodel.list',
@ -79,17 +79,25 @@ class Risotto(Controller):
""" """
cache_file = join(self.cache_root_path, str(servermodel_id), "dictionaries.xml") cache_file = join(self.cache_root_path, str(servermodel_id), "dictionaries.xml")
funcs_file = self.get_funcs_filename(servermodel_id) funcs_file = self.get_funcs_filename(servermodel_id)
log.info_msg(risotto_context, await log.info_msg(risotto_context,
None, None,
f'Load servermodel {servermodel_name} ({servermodel_id})') f'Load servermodel {servermodel_name} ({servermodel_id})')
# use file in cache # use file in cache
with open(cache_file) as fileio: with open(cache_file) as fileio:
xmlroot = parse(fileio).getroot() xmlroot = parse(fileio).getroot()
try:
self.servermodel[servermodel_id] = await self.build_metaconfig(servermodel_id, self.servermodel[servermodel_id] = await self.build_metaconfig(servermodel_id,
servermodel_name, servermodel_name,
xmlroot, xmlroot,
funcs_file) 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)
async def build_metaconfig(self, async def build_metaconfig(self,
servermodel_id: int, servermodel_id: int,
@ -148,17 +156,15 @@ class Risotto(Controller):
if servermodel_parent_id is None: if servermodel_parent_id is None:
return return
if not self.servermodel.get(servermodel_parent_id): 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})') msg = _(f'Servermodel with id {servermodel_parent_id} not loaded, skipping legacy for servermodel {servermodel_name} ({servermodel_id})')
log.error_msg(risotto_context, await log.error_msg(risotto_context,
None, None,
msg) msg)
return return
servermodel_parent = self.servermodel[servermodel_parent_id] servermodel_parent = self.servermodel[servermodel_parent_id]
servermodel_parent_name = await servermodel_parent.information.get('servermodel_name') servermodel_parent_name = await 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})') msg = _(f'Create legacy of servermodel {servermodel_name} ({servermodel_id}) with parent {servermodel_parent_name} ({servermodel_parent_id})')
log.info_msg(risotto_context, await log.info_msg(risotto_context,
None, None,
msg) msg)
@ -167,8 +173,7 @@ class Risotto(Controller):
try: try:
await mix.config.add(self.servermodel[servermodel_id]) await mix.config.add(self.servermodel[servermodel_id])
except Exception as err: except Exception as err:
if DEBUG: await log.error_msg(risotto_context,
log.error_msg(risotto_context,
None, None,
str(err)) str(err))
@ -176,7 +181,7 @@ class Risotto(Controller):
risotto_context: Context) -> None: risotto_context: Context) -> None:
""" load all available servers """ load all available servers
""" """
log.info_msg(risotto_context, await log.info_msg(risotto_context,
None, None,
f'Load servers') f'Load servers')
# get all servers # get all servers
@ -190,12 +195,12 @@ class Risotto(Controller):
server['server_name'], server['server_name'],
server['server_servermodel_id']) server['server_servermodel_id'])
except Exception as err: except Exception as err:
if DEBUG: if get_config().get('global').get('debug'):
print_exc() print_exc()
server_name = server['server_name'] server_name = server['server_name']
server_id = server['server_id'] server_id = server['server_id']
msg = _(f'unable to load server {server_name} ({server_id}): {err}') msg = _(f'unable to load server {server_name} ({server_id}): {err}')
log.error_msg(risotto_context, await log.error_msg(risotto_context,
None, None,
msg) msg)
@ -208,12 +213,12 @@ class Risotto(Controller):
""" """
if server_id in self.server: if server_id in self.server:
return return
log.info_msg(risotto_context, await log.info_msg(risotto_context,
None, None,
f'Load server {server_name} ({server_id})') f'Load server {server_name} ({server_id})')
if not server_servermodel_id in self.servermodel: if not server_servermodel_id in self.servermodel:
msg = f'unable to find servermodel with id {server_servermodel_id}' msg = f'unable to find servermodel with id {server_servermodel_id}'
log.error_msg(risotto_context, await log.error_msg(risotto_context,
None, None,
msg) msg)
raise CallError(msg) raise CallError(msg)
@ -364,13 +369,18 @@ class Risotto(Controller):
# await child.information.get('servermodel_id'), # await child.information.get('servermodel_id'),
# servermodel_id) # servermodel_id)
@register('v1.config.configuration.server.get', None) @register('v1.config.configuration.server.get')
async def get_configuration(self, async def get_configuration(self,
server_id: int, risotto_context: Context,
server_name: str,
deployed: bool) -> bytes: 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: if server_id not in self.server:
msg = _(f'cannot find server with id {server_id}') msg = _(f'cannot find server with id {server_id}')
log.error_msg(risotto_context, await log.error_msg(risotto_context,
None, None,
msg) msg)
raise CallError(msg) raise CallError(msg)
@ -382,25 +392,32 @@ class Risotto(Controller):
await server.property.read_only() await server.property.read_only()
try: try:
configuration = await server.value.dict(fullpath=True) configuration = await server.value.dict(fullpath=True,
leader_to_list=True)
except: except:
if deployed: if deployed:
msg = _(f'No configuration available for server {server_id}') msg = _(f'No configuration available for server {server_id}')
else: else:
msg = _(f'No undeployed configuration available for server {server_id}') msg = _(f'No undeployed configuration available for server {server_id}')
log.error_msg(risotto_context, await log.error_msg(risotto_context,
None, None,
msg) msg)
raise CallError(msg) raise CallError(msg)
return {'server_id': server_id, return {'server_name': server_name,
'deployed': deployed, 'deployed': deployed,
'configuration': configuration} 'configuration': configuration}
@register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated') @register('v1.config.configuration.server.deploy', 'v1.config.configuration.server.updated')
async def deploy_configuration(self, 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 """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 = self.server[server_id]['server']
config_std = self.server[server_id]['server_to_deploy'] config_std = self.server[server_id]['server_to_deploy']
@ -420,4 +437,5 @@ class Risotto(Controller):
await config.property.importation(await config_std.property.exportation()) await config.property.importation(await config_std.property.exportation())
return {'server_id': server_id, return {'server_id': server_id,
'server_name': server_name,
'deployed': True} 'deployed': True}

View File

@ -7,7 +7,7 @@ from ...utils import _
class Risotto(Controller): class Risotto(Controller):
@register('v1.server.list', None, database=True) @register('v1.server.list')
async def server_list(self, async def server_list(self,
risotto_context: Context) -> Dict: risotto_context: Context) -> Dict:
sql = ''' sql = '''
@ -17,16 +17,19 @@ class Risotto(Controller):
servers = await risotto_context.connection.fetch(sql) servers = await risotto_context.connection.fetch(sql)
return [dict(r) for r in servers] return [dict(r) for r in servers]
@register('v1.server.create', 'v1.server.created', database=True) @register('v1.server.create', 'v1.server.created')
async def server_create(self, async def server_create(self,
risotto_context: Context, risotto_context: Context,
server_name: str, server_name: str,
server_description: str, server_description: str,
server_servermodel_id: int) -> Dict: servermodel_name: str,
source_name: str,
await self.call('v1.servermodel.get_by_id', release_distribution: str) -> Dict:
servermodel = await self.call('v1.servermodel.describe',
risotto_context, risotto_context,
servermodel_id=server_servermodel_id) servermodel_name=servermodel_name,
source_name=source_name,
release_distribution=release_distribution)
server_insert = """INSERT INTO Server(ServerName, ServerDescription, ServerServermodelId) server_insert = """INSERT INTO Server(ServerName, ServerDescription, ServerServermodelId)
VALUES ($1,$2,$3) VALUES ($1,$2,$3)
RETURNING ServerId RETURNING ServerId
@ -34,13 +37,19 @@ class Risotto(Controller):
server_id = await risotto_context.connection.fetchval(server_insert, server_id = await risotto_context.connection.fetchval(server_insert,
server_name, server_name,
server_description, server_description,
server_servermodel_id) 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, return {'server_id': server_id,
'server_name': server_name, 'server_name': server_name,
'server_description': server_description, 'server_description': server_description,
'server_servermodel_id': server_servermodel_id} 'server_servermodel_id': servermodel['servermodel_id']}
@register('v1.server.describe', None, database=True) @register('v1.server.describe')
async def server_describe(self, async def server_describe(self,
risotto_context: Context, risotto_context: Context,
server_name: str) -> Dict: server_name: str) -> Dict:

View File

@ -31,16 +31,17 @@ class Risotto(Controller):
source_url='none') source_url='none')
internal_release = await self.call('v1.source.release.create', internal_release = await self.call('v1.source.release.create',
risotto_context, risotto_context,
source_id=internal_source['source_id'], source_name='internal',
release_name='none', release_name='none',
release_distribution='stable') release_distribution='last')
self.internal_release_id = internal_release['release_id'] self.internal_release_id = internal_release['release_id']
def servermodel_gen_funcs(self, async def servermodel_gen_funcs(self,
servermodel_name: str, servermodel_name: str,
servermodel_id: int, servermodel_id: int,
dependencies: Dict, dependencies: Dict,
release_cache: Dict) -> None: release_cache: Dict,
risotto_context: Context) -> None:
as_names = [] as_names = []
dest_file = self.get_servermodel_cache(servermodel_id, 'funcs.py') dest_file = self.get_servermodel_cache(servermodel_id, 'funcs.py')
with open(dest_file, 'wb') as funcs: with open(dest_file, 'wb') as funcs:
@ -65,14 +66,16 @@ class Risotto(Controller):
funcs.write(b'\n') funcs.write(b'\n')
as_names_str = '", "'.join(as_names) as_names_str = '", "'.join(as_names)
log.info(_(f'gen funcs for "{servermodel_name}" with application services "{as_names_str}"')) await log.info(risotto_context,
_(f'gen funcs for "{servermodel_name}" with application services "{as_names_str}"'))
eolobj = CreoleObjSpace(dtdfilename) eolobj = CreoleObjSpace(dtdfilename)
def servermodel_gen_schema(self, async def servermodel_gen_schema(self,
servermodel_name: str, servermodel_name: str,
servermodel_id: int, servermodel_id: int,
dependencies: Dict, dependencies: Dict,
release_cache: Dict) -> None: release_cache: Dict,
risotto_context: Context) -> None:
paths = [] paths = []
extras = [] extras = []
as_names = set() as_names = set()
@ -105,7 +108,8 @@ class Risotto(Controller):
extras.append((namespace, [extra_dir])) extras.append((namespace, [extra_dir]))
eolobj = CreoleObjSpace(dtdfilename) eolobj = CreoleObjSpace(dtdfilename)
as_names_str = '", "'.join(as_names) as_names_str = '", "'.join(as_names)
log.info(_(f'gen schema for "{servermodel_name}" with application services "{as_names_str}"')) 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) eolobj.create_or_populate_from_xml('creole', paths)
for extra in extras: for extra in extras:
eolobj.create_or_populate_from_xml(extra[0], extra[1]) eolobj.create_or_populate_from_xml(extra[0], extra[1])
@ -122,11 +126,12 @@ class Risotto(Controller):
return join(self.cache_root_path, str(servermodel_id), subdir) return join(self.cache_root_path, str(servermodel_id), subdir)
return join(self.cache_root_path, str(servermodel_id)) return join(self.cache_root_path, str(servermodel_id))
def servermodel_copy_templates(self, async def servermodel_copy_templates(self,
servermodel_name: str, servermodel_name: str,
servermodel_id: int, servermodel_id: int,
dependencies: Dict, dependencies: Dict,
release_cache: Dict) -> None: release_cache: Dict,
risotto_context: Context) -> None:
as_names = [] as_names = []
dest_dir = self.get_servermodel_cache(servermodel_id, 'templates') dest_dir = self.get_servermodel_cache(servermodel_id, 'templates')
makedirs(dest_dir) makedirs(dest_dir)
@ -147,7 +152,8 @@ class Risotto(Controller):
copyfile(join(path, template), template_path) copyfile(join(path, template), template_path)
as_names.append(applicationservice_name) as_names.append(applicationservice_name)
as_names_str = '", "'.join(as_names) as_names_str = '", "'.join(as_names)
log.info(_(f'copy templates for "{servermodel_name}" with application services "{as_names_str}"')) await log.info(risotto_context,
_(f'copy templates for "{servermodel_name}" with application services "{as_names_str}"'))
async def _servermodel_create(self, async def _servermodel_create(self,
risotto_context: Context, risotto_context: Context,
@ -193,18 +199,21 @@ class Risotto(Controller):
risotto_context, risotto_context,
release_id=as_release_id) release_id=as_release_id)
self.servermodel_gen_funcs(servermodel_name, await self.servermodel_gen_funcs(servermodel_name,
servermodel_id, servermodel_id,
dependencies, dependencies,
release_cache) release_cache,
self.servermodel_gen_schema(servermodel_name, risotto_context)
await self.servermodel_gen_schema(servermodel_name,
servermodel_id, servermodel_id,
dependencies, dependencies,
release_cache) release_cache,
self.servermodel_copy_templates(servermodel_name, risotto_context)
await self.servermodel_copy_templates(servermodel_name,
servermodel_id, servermodel_id,
dependencies, dependencies,
release_cache) release_cache,
risotto_context)
sm_dict = {'servermodel_name': servermodel_name, sm_dict = {'servermodel_name': servermodel_name,
'servermodel_description': servermodel_description, 'servermodel_description': servermodel_description,
'servermodel_parents_id': servermodel_parents_id, 'servermodel_parents_id': servermodel_parents_id,
@ -236,7 +245,7 @@ class Risotto(Controller):
servermodel_name, servermodel_name,
release_id)['servermodel_id'] release_id)['servermodel_id']
@register('v1.servermodel.dataset.updated', None, database=True) @register('v1.servermodel.dataset.updated')
async def servermodel_update(self, async def servermodel_update(self,
risotto_context: Context, risotto_context: Context,
source_name: str, source_name: str,
@ -313,7 +322,7 @@ class Risotto(Controller):
servermodel_description['done'] = True servermodel_description['done'] = True
return {'retcode': 0, 'returns': _('Servermodels successfully loaded')} return {'retcode': 0, 'returns': _('Servermodels successfully loaded')}
@register('v1.servermodel.list', None, database=True) @register('v1.servermodel.list')
async def servermodel_list(self, async def servermodel_list(self,
risotto_context: Context, risotto_context: Context,
source_id: int): source_id: int):
@ -324,10 +333,32 @@ class Risotto(Controller):
servermodels = await risotto_context.connection.fetch(sql) servermodels = await risotto_context.connection.fetch(sql)
return [dict(r) for r in servermodels] return [dict(r) for r in servermodels]
@register('v1.servermodel.get_by_id', None, database=True) @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, async def servermodel_get_by_id(self,
risotto_context: Context, risotto_context: Context,
servermodel_id: int): servermodel_id: int) -> Dict:
sql = ''' sql = '''
SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id SELECT ServermodelId as servermodel_id, ServermodelName as servermodel_name, ServermodelDescription as servermodel_description, ServermodelParentsId as servermodel_parents_id, ServermodelReleaseId as release_id
FROM Servermodel FROM Servermodel

View File

@ -6,7 +6,6 @@ from tiramisu import Storage
from ...http import register as register_http from ...http import register as register_http
from ...config import DEBUG
from ...context import Context from ...context import Context
from ...utils import _ from ...utils import _
from .storage import storage_server, storage_servermodel from .storage import storage_server, storage_servermodel
@ -64,24 +63,22 @@ class Risotto(Controller):
'mode': session['mode'], 'mode': session['mode'],
'debug': session['debug']} 'debug': session['debug']}
@register(['v1.session.server.start', 'v1.session.servermodel.start'], None) @register('v1.session.server.start')
async def start_session(self, async def start_session_server(self,
risotto_context: Context, risotto_context: Context,
id: int) -> Dict: server_name: str) -> Dict:
""" start a new config session for a server or a servermodel """ start a new config session for a server
""" """
type = risotto_context.message.rsplit('.', 2)[-2]
config_module = dispatcher.get_service('config') config_module = dispatcher.get_service('config')
if type == 'server': server = await self.call('v1.server.describe',
if id not in config_module.server: risotto_context,
raise Exception(_(f'cannot find {type} with id {id}')) server_name=server_name)
config = config_module.server[id]['server'] if not server or server['server_id'] not in config_module.server:
else: raise Exception(_(f'cannot find server with name {server_name}'))
if id not in config_module.servermodel: id = server['server_id']
raise Exception(_(f'cannot find {type} with id {id}')) config = config_module.server[id]['server_to_deploy']
config = config_module.servermodel[id]
storage = self.get_storage(type) storage = self.get_storage('server')
# check if a session already exists # check if a session already exists
sessions = storage.get_sessions() sessions = storage.get_sessions()
@ -108,9 +105,57 @@ class Risotto(Controller):
# return session's information # return session's information
return self.get_session_informations(risotto_context, return self.get_session_informations(risotto_context,
session_id, session_id,
type) 'server')
@register(['v1.session.server.list', 'v1.session.servermodel.list'], None) @register('v1.session.servermodel.start')
async def start_session_servermodel(self,
risotto_context: Context,
servermodel_name: str,
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, async def list_session_server(self,
risotto_context: Context) -> Dict: risotto_context: Context) -> Dict:
type = risotto_context.message.rsplit('.', 2)[-2] type = risotto_context.message.rsplit('.', 2)[-2]
@ -118,7 +163,7 @@ class Risotto(Controller):
return [self.format_session(session_id, session) for session_id, session in storage.get_sessions().items()] 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, async def filter_session(self,
risotto_context: Context, risotto_context: Context,
session_id: str, session_id: str,
@ -145,7 +190,7 @@ class Risotto(Controller):
session_id, session_id,
type) 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, async def configure_session(self,
risotto_context: Context, risotto_context: Context,
session_id: str, session_id: str,
@ -182,7 +227,7 @@ class Risotto(Controller):
ret['index'] = index ret['index'] = index
return ret 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, async def validate_session(self,
risotto_context: Context, risotto_context: Context,
session_id: str) -> Dict: session_id: str) -> Dict:
@ -211,7 +256,7 @@ class Risotto(Controller):
return self.format_session(session_id, return self.format_session(session_id,
session) 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, async def get_session_server(self,
risotto_context: Context, risotto_context: Context,
session_id: str, session_id: str,
@ -229,7 +274,7 @@ class Risotto(Controller):
info['content'] = content info['content'] = content
return info 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, async def stop_session(self,
risotto_context: Context, risotto_context: Context,
session_id: str, session_id: str,
@ -241,7 +286,7 @@ class Risotto(Controller):
id_ = session['id'] id_ = session['id']
config_module = dispatcher.get_service('config') config_module = dispatcher.get_service('config')
if type == 'server': if type == 'server':
config = config_module.server[id_]['server'] config = config_module.server[id_]['server_to_deploy']
else: else:
config = config_module.servermodel[id_] config = config_module.servermodel[id_]
if save: if save:

View File

@ -10,7 +10,7 @@ from ...config import get_config
class Risotto(Controller): class Risotto(Controller):
@register('v1.source.create', None, database=True) @register('v1.source.create')
async def source_create(self, async def source_create(self,
risotto_context: Context, risotto_context: Context,
source_name: str, source_name: str,
@ -43,7 +43,7 @@ class Risotto(Controller):
'source_url': source_url, 'source_url': source_url,
'source_id': source_id} 'source_id': source_id}
@register('v1.source.describe', None, database=True) @register('v1.source.describe')
async def source_describe(self, async def source_describe(self,
risotto_context: Context, risotto_context: Context,
source_name: str) -> Dict: source_name: str) -> Dict:
@ -57,7 +57,7 @@ class Risotto(Controller):
raise Exception(_(f'unknown source with name {source_name}')) raise Exception(_(f'unknown source with name {source_name}'))
return dict(source) return dict(source)
@register('v1.source.list', None, database=True) @register('v1.source.list')
async def source_list(self, async def source_list(self,
risotto_context: Context) -> List[Dict]: risotto_context: Context) -> List[Dict]:
source_list = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url source_list = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
@ -66,7 +66,7 @@ class Risotto(Controller):
result = await risotto_context.connection.fetch(source_list) result = await risotto_context.connection.fetch(source_list)
return [dict(r) for r in result] return [dict(r) for r in result]
@register('v1.source.dataset.update', None, database=True) @register('v1.source.dataset.update')
async def version_update(self, async def version_update(self,
risotto_context: Context, risotto_context: Context,
source_id: int, source_id: int,
@ -81,25 +81,28 @@ class Risotto(Controller):
return {'release_id': release_id, return {'release_id': release_id,
'release_name': release_name} 'release_name': release_name}
@register('v1.source.release.create', None, database=True) @register('v1.source.release.create')
async def source_release_create(self, async def source_release_create(self,
risotto_context: Context, risotto_context: Context,
source_id: int, source_name: str,
release_name: str, release_name: str,
release_distribution: str) -> Dict: release_distribution: str) -> Dict:
source_get = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url source_get = """SELECT SourceId as source_id, SourceName as source_name, SourceURL as source_url
FROM Source FROM Source
WHERE SourceId = $1 WHERE SourceName = $1
""" """
release_upsert = """INSERT INTO Release(ReleaseName, ReleaseSourceId, ReleaseDistribution) VALUES ($1, $2, $3) release_upsert = """INSERT INTO Release(ReleaseName, ReleaseSourceId, ReleaseDistribution) VALUES ($1, $2, $3)
ON CONFLICT (ReleaseName, ReleaseSourceId) DO UPDATE SET ReleaseName = $1 ON CONFLICT (ReleaseName, ReleaseSourceId) DO UPDATE SET ReleaseName = $1
RETURNING ReleaseId RETURNING ReleaseId
""" """
source = dict(await risotto_context.connection.fetchrow(source_get, source_obj = await risotto_context.connection.fetchrow(source_get,
source_id)) 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_id = await risotto_context.connection.fetchval(release_upsert,
release_name, release_name,
source_id, source['source_id'],
release_distribution) release_distribution)
del source['source_id'] del source['source_id']
source['release_id'] = release_id source['release_id'] = release_id
@ -107,16 +110,33 @@ class Risotto(Controller):
source['release_distribution'] = release_distribution source['release_distribution'] = release_distribution
return source return source
@register('v1.source.release.list', None, database=True) @register('v1.source.release.list')
async def release_list(self, async def release_list(self,
risotto_context): 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 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 FROM Release, Source
WHERE Source.SourceId=Release.ReleaseSourceId""" WHERE Source.SourceName=$1 AND Source.SourceId=Release.ReleaseSourceId"""
result = await risotto_context.connection.fetch(release_query) result = await risotto_context.connection.fetch(release_query,
source_name)
return [dict(r) for r in result] return [dict(r) for r in result]
@register('v1.source.release.get_by_id', None, database=True) @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, async def release_get_by_id(self,
risotto_context: Context, risotto_context: Context,
release_id: int) -> Dict: release_id: int) -> Dict:
@ -129,7 +149,7 @@ class Risotto(Controller):
raise Exception(_(f'unknown release id {release_id}')) raise Exception(_(f'unknown release id {release_id}'))
return dict(result) return dict(result)
@register('v1.source.release.get_by_distribution', None, database=True) @register('v1.source.release.get_by_distribution')
async def release_get_by_distribution(self, async def release_get_by_distribution(self,
risotto_context: Context, risotto_context: Context,
source_id: int, source_id: int,

View File

@ -17,7 +17,7 @@ class Risotto(Controller):
self.storage = Storage(engine='dictionary') self.storage = Storage(engine='dictionary')
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel') 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, async def template_get(self,
risotto_context, risotto_context,
server_name: str) -> Dict: server_name: str) -> Dict:
@ -28,10 +28,13 @@ class Risotto(Controller):
servermodel_id = server['server_servermodel_id'] servermodel_id = server['server_servermodel_id']
config_module = dispatcher.get_service('config') config_module = dispatcher.get_service('config')
server = config_module.server[server_id] 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: while True:
try: try:
children = list(config.config.list()) children = list(await config.config.list())
except: except:
break break
if children: if children:
@ -48,11 +51,11 @@ class Risotto(Controller):
rmtree(tmp_dir) rmtree(tmp_dir)
mkdir(tmp_dir) mkdir(tmp_dir)
templates_dir = join(self.cache_root_path, str(servermodel_id), 'templates') templates_dir = join(self.cache_root_path, str(servermodel_id), 'templates')
generate(config, await generate(config,
server['funcs_file'], server['funcs_file'],
templates_dir, templates_dir,
tmp_dir, tmp_dir,
configurations_dir) configurations_dir)
return {'server_id': server_id, return {'server_name': server_name,
'template_dir': configurations_dir} '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)