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
```
podman pull docker.io/library/postgres:11-alpine
podman run -dt -p 5432:5432 postgres:11-alpine
docker run -dt -p 5432:5432 --name postgres 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 DATABASE risotto;"
@ -35,3 +35,38 @@ Gestion de la base de données avec Sqitch
cpanm --quiet --notest App::Sqitch
sqitch init risotto --uri https://forge.cadoles.com/Infra/risotto --engine pg
```
Commande :
# Empty database:
su - postgres
psql -U postgres risotto
drop table log; drop table userrole; drop table release; drop table source; drop table server; drop table servermodel; drop table applicationservice; drop table roleuri; drop table risottouser; drop table uri;
# Import EOLE
./script/cucchiaiata source.create -n eole -u http://localhost
./script/cucchiaiata source.release.create -s eole -n 2.7.1.1 -d last
./script/cucchiaiata applicationservice.dataset.updated -s eole -r last
./script/cucchiaiata servermodel.dataset.updated -s eole -r last
# Create a server
./script/cucchiaiata server.create -s test -d description -m eolebase -n eole -r last
# Configure the server
./script/cucchiaiata session.server.start -s test
S=xxxxxxxxxxxxxxxxxxxxxx
./script/cucchiaiata session.server.configure -s $S --creole.reseau.unbound_ip_address_cidr 192.168.1.1/24 --creole.reseau.unbound_route_address 192.168.1.2 --creole.serveur_dns.unbound_allowed_client_cidr 192.168.1.0/24 --creole.serveur_dns.unbound_local_zones cadoles.com
./script/cucchiaiata session.server.configure -s $S --creole.reseau.unbound_domain_name test.cadoles.com
./script/cucchiaiata session.server.filter -s $S -n unbound
./script/cucchiaiata session.server.configure -s $S --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.hostname_cadoles_com toto titi --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.ip_cadoles_com 0 192.168.1.25 --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.type_cadoles_com 1 CNAME --unbound.unbound_zone_cadoles_com.hostname_cadoles_com.cname_cadoles_com 1 toto.cadoles.com
./script/cucchiaiata session.server.validate -s $S
./script/cucchiaiata session.server.stop -s $S -a
# Generate configuration
./script/cucchiaiata config.configuration.server.deploy -s test
./script/cucchiaiata template.generate -s test
# Create a new user and set role 'server_rw' for this server
./script/cucchiaiata user.create -l gnunux -n gnunux -s gnunux
./script/cucchiaiata user.role.create -u gnunux -n 'server_rw' -a 'Server.ServerName' -v test

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
public: true
parameters:
applicationservice_name:
type: String

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,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
public: true
parameters:
server_name:
type: String
@ -16,11 +14,21 @@ parameters:
type: String
shortarg: d
description: Description du serveur.
server_servermodel_id:
type: Number
servermodel_name:
type: String
shortarg: m
ref: Servermodel.ServermodelId
description: Identifiant du modèle de serveur.
ref: Servermodel.ServermodelName
description: Nom du modèle de serveur.
source_name:
type: String
shortarg: n
ref: Source.SourceName
description: Nom de la source.
release_distribution:
type: String
shortarg: r
ref: Source.ReleaseDistribution
description: Nom de la sous-version.
response:
type: Server

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,8 +5,6 @@ description: |
pattern: rpc
public: true
parameters:
source_name:
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
public: false
parameters:
servermodel_id:
type: Number

View File

@ -6,8 +6,6 @@ description: |
pattern: rpc
public: true
parameters:
source_id:
type: Number
@ -19,13 +17,3 @@ parameters:
response:
type: '[]Servermodel'
description: Liste des modèles de serveur disponibles.
errors:
- uri: servermodel.list.error.database_not_available
related:
- servermodel.describe
- servermodel.create
- servermodel.update
- servermodel.delete
- servermodel.event

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,15 +5,22 @@ description: |
pattern: rpc
public: true
parameters:
id:
type: Number
ref: Servermodel.ServermodelId
shortarg: c
description: |
Identifiant de la configuration.
servermodel_name:
type: String
ref: Servermodel.ServermodelName
shortarg: s
description: Nom du serveurmodel.
source_name:
type: String
shortarg: n
description: Nom de la source.
ref: Source.SourceName
release_distribution:
type: String
shortarg: r
description: Nom de la distribution.
ref: Source.ReleaseDistribution
response:
type: Session

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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
description: Les fichiers de configuration générés.
properties:
server_id:
type: Number
description: Identifiant du serveur.
server_name:
type: String
description: Nom du serveur.
ref: Server.ServerName
template_dir:
type: String
description: Nom du répertoire avec les fichiers de configuration générés.
required:
- server_id
- server_name
- template_dir

View File

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

View File

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

View File

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

7
requirements.txt Normal file
View File

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

View File

@ -82,11 +82,30 @@ CREATE TABLE RoleURI (
PRIMARY KEY (RoleName, URIId)
);
-- Log table creation
CREATE TABLE log(
Msg VARCHAR(255) NOT NULL,
Level VARCHAR(10) NOT NULL,
Path VARCHAR(255),
Username VARCHAR(100) NOT NULL,
Data JSON,
Date timestamp DEFAULT current_timestamp
);
"""
async def main():
db_conf = get_config().get('database')
pool = await asyncpg.create_pool(database=db_conf.get('dbname'), user=db_conf.get('user'))
#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 connection.transaction():
returns = await connection.execute(VERSION_INIT)
@ -95,3 +114,4 @@ if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# asyncio.run(main())

View File

@ -1,32 +1,34 @@
HTTP_PORT = 8080
MESSAGE_ROOT_PATH = 'messages'
DEBUG = True
DATABASE_DIR = 'database'
DATABASE_DIR = '/var/cache/risotto/database'
INTERNAL_USER = 'internal'
CONFIGURATION_DIR = 'configurations'
TEMPLATE_DIR = 'templates'
TMP_DIR = 'tmp'
ROUGAIL_DTD_PATH = '../rougail/data/creole.dtd'
POSTGRESQL_ADDRESS = '192.168.56.106'
POSTGRESQL_PORT = 5432
DEFAULT_USER = 'Anonymous'
import os
from pathlib import PurePosixPath
CURRENT_PATH = PurePosixPath(__file__)
def get_config():
return {'database': {'host': 'localhost',
return {'database': {'engine': 'postgres',
'host': 'postgres',
'port': 5432,
'dbname': 'risotto',
'user': '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',
'debug': DEBUG,
'debug': True,
'internal_user': 'internal',
'check_role': False,
'rougail_dtd_path': '../rougail/data/creole.dtd'},
'check_role': True,
'rougail_dtd_path': '../rougail/data/creole.dtd',
'admin_user': DEFAULT_USER},
'source': {'root_path': '/srv/seed'},
'cache': {'root_path': '/var/cache/risotto'}
}

View File

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

View File

@ -10,13 +10,14 @@ from .context import Context
from .error import CallError, NotAllowedError, RegistrationError
from .message import get_messages
from .logger import log
from .config import DEBUG, HTTP_PORT
from .config import get_config
from .services import load_services
def create_context(request):
risotto_context = Context()
risotto_context.username = request.match_info.get('username', "Anonymous")
risotto_context.username = request.match_info.get('username',
get_config()['http_server']['default_user'])
return risotto_context
@ -52,11 +53,11 @@ class extra_route_handler:
except CallError as err:
raise HTTPBadRequest(reason=str(err))
except Exception as err:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise HTTPInternalServerError(reason=str(err))
log.info_msg(kwargs['risotto_context'],
dict(request.match_info))
# await log.info_msg(kwargs['risotto_context'],
# dict(request.match_info))
return Response(text=dumps(returns))
@ -80,7 +81,7 @@ async def handle(request):
except CallError as err:
raise HTTPBadRequest(reason=str(err).replace('\n', ' '))
except Exception as err:
if DEBUG:
if get_config()['global']['debug']:
print_exc()
raise HTTPInternalServerError(reason=str(err))
return Response(text=dumps({'response': text}))
@ -89,8 +90,19 @@ async def handle(request):
async def api(request, risotto_context):
global tiramisu
if not tiramisu:
config = await Config(get_messages(load_shortarg=True,
only_public=True)[1])
# check all URI that have an associated role
# all URI without role is concidered has a private URI
uris = []
async with dispatcher.pool.acquire() as connection:
async with connection.transaction():
# Check role with ACL
sql = '''
SELECT URI.URIName
FROM URI, RoleURI
WHERE RoleURI.URIId = URI.URIId
'''
uris = [uri['uriname'] for uri in await connection.fetch(sql)]
config = await Config(get_messages(load_shortarg=True, uris=uris)[1])
await config.property.read_write()
tiramisu = await config.option.dict(remotable='none')
return tiramisu
@ -113,11 +125,8 @@ async def get_app(loop):
print(_('======== Registered messages ========'))
for message in messages:
web_message = f'/api/{version}/{message}'
if dispatcher.messages[version][message]['public']:
print(f' - {web_message}')
else:
pattern = dispatcher.messages[version][message]['pattern']
print(f' - {web_message} (private {pattern})')
print(f' - {web_message} ({pattern})')
routes.append(post(web_message, handle))
print()
print(_('======== Registered extra routes ========'))
@ -133,7 +142,7 @@ async def get_app(loop):
del extra_routes
app.add_routes(routes)
await dispatcher.on_join()
return await loop.create_server(app.make_handler(), '*', HTTP_PORT)
return await loop.create_server(app.make_handler(), '*', get_config()['http_server']['port'])
tiramisu = None

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ from ...utils import _
class Risotto(Controller):
@register('v1.server.list', None, database=True)
@register('v1.server.list')
async def server_list(self,
risotto_context: Context) -> Dict:
sql = '''
@ -17,16 +17,19 @@ class Risotto(Controller):
servers = await risotto_context.connection.fetch(sql)
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,
risotto_context: Context,
server_name: str,
server_description: str,
server_servermodel_id: int) -> Dict:
await self.call('v1.servermodel.get_by_id',
servermodel_name: str,
source_name: str,
release_distribution: str) -> Dict:
servermodel = await self.call('v1.servermodel.describe',
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)
VALUES ($1,$2,$3)
RETURNING ServerId
@ -34,13 +37,19 @@ class Risotto(Controller):
server_id = await risotto_context.connection.fetchval(server_insert,
server_name,
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,
'server_name': server_name,
'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,
risotto_context: Context,
server_name: str) -> Dict:

View File

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

View File

@ -6,7 +6,6 @@ from tiramisu import Storage
from ...http import register as register_http
from ...config import DEBUG
from ...context import Context
from ...utils import _
from .storage import storage_server, storage_servermodel
@ -64,24 +63,22 @@ class Risotto(Controller):
'mode': session['mode'],
'debug': session['debug']}
@register(['v1.session.server.start', 'v1.session.servermodel.start'], None)
async def start_session(self,
@register('v1.session.server.start')
async def start_session_server(self,
risotto_context: Context,
id: int) -> Dict:
""" start a new config session for a server or a servermodel
server_name: str) -> Dict:
""" start a new config session for a server
"""
type = risotto_context.message.rsplit('.', 2)[-2]
config_module = dispatcher.get_service('config')
if type == 'server':
if id not in config_module.server:
raise Exception(_(f'cannot find {type} with id {id}'))
config = config_module.server[id]['server']
else:
if id not in config_module.servermodel:
raise Exception(_(f'cannot find {type} with id {id}'))
config = config_module.servermodel[id]
server = await self.call('v1.server.describe',
risotto_context,
server_name=server_name)
if not server or server['server_id'] not in config_module.server:
raise Exception(_(f'cannot find server with name {server_name}'))
id = server['server_id']
config = config_module.server[id]['server_to_deploy']
storage = self.get_storage(type)
storage = self.get_storage('server')
# check if a session already exists
sessions = storage.get_sessions()
@ -108,9 +105,57 @@ class Risotto(Controller):
# return session's information
return self.get_session_informations(risotto_context,
session_id,
type)
'server')
@register(['v1.session.server.list', 'v1.session.servermodel.list'], None)
@register('v1.session.servermodel.start')
async def start_session_servermodel(self,
risotto_context: Context,
servermodel_name: str,
source_name: str,
release_distribution: str) -> Dict:
""" start a new config session for a server or a servermodel
"""
config_module = dispatcher.get_service('config')
servermodel = await self.call('v1.servermodel.describe',
risotto_context,
servermodel_name=servermodel_name,
source_name=source_name,
release_distribution=release_distribution)
if not servermodel or servermodel['servermodel_id'] not in config_module.servermodel:
raise Exception(_(f'cannot find servermodel with name {servermodel_name}'))
id = servermodel['servermodel_id']
config = config_module.servermodel[id]
storage = self.get_storage('servermodel')
# check if a session already exists
sessions = storage.get_sessions()
for sess_id, session in sessions.items():
if session['id'] == id:
if session['username'] == risotto_context.username:
# same user so returns it
return self.format_session(sess_id,
session)
else:
raise Exception(_(f'{username} already edits this configuration'))
# create a new session
while True:
session_id = 'z' + hexlify(urandom(23)).decode()
if not session_id in sessions:
break
await storage.add_session(session_id,
config,
id,
risotto_context.username,
self.modify_storage)
# return session's information
return self.get_session_informations(risotto_context,
session_id,
'servermodel')
@register(['v1.session.server.list', 'v1.session.servermodel.list'])
async def list_session_server(self,
risotto_context: Context) -> Dict:
type = risotto_context.message.rsplit('.', 2)[-2]
@ -118,7 +163,7 @@ class Risotto(Controller):
return [self.format_session(session_id, session) for session_id, session in storage.get_sessions().items()]
@register(['v1.session.server.filter', 'v1.session.servermodel.filter'], None)
@register(['v1.session.server.filter', 'v1.session.servermodel.filter'])
async def filter_session(self,
risotto_context: Context,
session_id: str,
@ -145,7 +190,7 @@ class Risotto(Controller):
session_id,
type)
@register(['v1.session.server.configure', 'v1.session.servermodel.configure'], None)
@register(['v1.session.server.configure', 'v1.session.servermodel.configure'])
async def configure_session(self,
risotto_context: Context,
session_id: str,
@ -182,7 +227,7 @@ class Risotto(Controller):
ret['index'] = index
return ret
@register(['v1.session.server.validate', 'v1.session.servermodel.validate'], None)
@register(['v1.session.server.validate', 'v1.session.servermodel.validate'])
async def validate_session(self,
risotto_context: Context,
session_id: str) -> Dict:
@ -211,7 +256,7 @@ class Risotto(Controller):
return self.format_session(session_id,
session)
@register(['v1.session.server.get', 'v1.session.servermodel.get'], None)
@register(['v1.session.server.get', 'v1.session.servermodel.get'])
async def get_session_server(self,
risotto_context: Context,
session_id: str,
@ -229,7 +274,7 @@ class Risotto(Controller):
info['content'] = content
return info
@register(['v1.session.server.stop', 'v1.session.servermodel.stop'], None)
@register(['v1.session.server.stop', 'v1.session.servermodel.stop'])
async def stop_session(self,
risotto_context: Context,
session_id: str,
@ -241,7 +286,7 @@ class Risotto(Controller):
id_ = session['id']
config_module = dispatcher.get_service('config')
if type == 'server':
config = config_module.server[id_]['server']
config = config_module.server[id_]['server_to_deploy']
else:
config = config_module.servermodel[id_]
if save:

View File

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

View File

@ -17,7 +17,7 @@ class Risotto(Controller):
self.storage = Storage(engine='dictionary')
self.cache_root_path = join(get_config().get('cache').get('root_path'), 'servermodel')
@register('v1.template.generate', None)
@register('v1.template.generate')
async def template_get(self,
risotto_context,
server_name: str) -> Dict:
@ -28,10 +28,13 @@ class Risotto(Controller):
servermodel_id = server['server_servermodel_id']
config_module = dispatcher.get_service('config')
server = config_module.server[server_id]
config = meta = server['server'].config.deepcopy(storage=self.storage)
export = await server['server'].value.exportation()
if not export[0]:
raise Exception(_(f'configuration for server "{server_name}" is empty, you should deploy it first'))
config = meta = await server['server'].config.deepcopy(storage=self.storage)
while True:
try:
children = list(config.config.list())
children = list(await config.config.list())
except:
break
if children:
@ -48,11 +51,11 @@ class Risotto(Controller):
rmtree(tmp_dir)
mkdir(tmp_dir)
templates_dir = join(self.cache_root_path, str(servermodel_id), 'templates')
generate(config,
await generate(config,
server['funcs_file'],
templates_dir,
tmp_dir,
configurations_dir)
return {'server_id': server_id,
return {'server_name': server_name,
'template_dir': configurations_dir}

View File

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

View File

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

View File

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

View File

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