rougail/doc/getting_started.md

23 KiB

Table of Contents

Construire une liste d'options

Rougail permet de construite des options Tiramisu à partir de dictionnaire écrit en YAML. Le principal avantage étant que l'écrire des options est beaucoup plus simple.

Une fois chargé, on retrouve toute la puissance de Tiramisu dans la gestion de la configuration.

Avant de commencer, il faut connaitre les spécificités du format de fichier YAML, des notions de Jinja et de Tiramisu.

La configuration du proxy type Mozilla Firefox

L'objectif de ce première tutorial est de reproduire cette page de paramètres de Mozilla Firefox :

Firefox parameters page

Les variables vont être créées dans plusieurs fichiers dans un but didactique. Bien évidement toutes les variables pourront être mise dans le même fichier.

La famille proxy

Nous allons classer toutes ces variables dans une famille. Cette famille s'appelera proxy. Créons le premier fichier dict/00-proxy.yml

---
version: '1.0'
proxy:
  description: Configure Proxy Access to the Internet
  type: family

Le type de la famille est ici précisé parce qu'on n'a pas de variable ou de famille à l'intérieur de cette famille. Le moteur pensera alors que c'est une simple variable.

Le type de proxy

Il est possible de définir plusieurs modes de configuration du proxy (de "pas de proxy" à "la configuration d'un fichier de configuration automatique").

Nous allons donc créer une première variable dans cette famille dont la description est "Proxy mode". Créons le deuxième fichier dict/01-proxy_mode.yml

---
version: '1.0'
proxy:
  proxy_mode:
    description: Proxy mode
    type: choice
    choices:
      - No proxy
      - Auto-detect proxy settings for this network
      - Use system proxy settings
      - Manual proxy configuration
      - Automatic proxy configuration URL
    default: No proxy
    mandatory: true

Cette variable nécessite une valeur (la valeur None n'est pas acceptable), elle est donc obligatoire (mandatory).

Si l'utilisateur ne précise pas de valeurs, la valeur de cette variable sera "No proxy" (default).

La variable est à choix (type: choice) donc la liste des valeurs disponible est contrainte (choices), seul les valeurs suivantes sont autorisés :

  • No proxy
  • Auto-detect proxy settings for this network
  • Use system proxy settings
  • Manual proxy configuration
  • Automatic proxy configuration URL

Testons nos deux premiers dictionnaires :

>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'No proxy'}

Le mode manuel

Toute la configuration manuelle du proxy sera classé dans une famille. Créons le fichier dict/02-proxy_manual.yml :

---
version: '1.0'
proxy:
  manual:
    description: Manual proxy configuration
    type: family
    disabled:
      type: jinja
      jinja: |
        {% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %}
        the mode proxy is not manual
        {% endif %}        

Si l'utilisateur choisi le mode de proxy "Manual proxy configuration", on veut voir apparaitre (disabled) une nouvelle sous-famille appelé manual. Si le template Jinja renvoi du texte, la famille sera alors désactivé. Sinon elle est accessible. Désactiver un famille signifie qu'on ne pourra pas y accèder ainsi qu'aux variables ou familles inclusent dans cette famille.

La configuration du proxy HTTP

Dans cette famille ajoutons une sous-famille http_proxy contenant les variables address et port. Créons le fichier dict/03-proxy_manual_http_proxy.yml :

---
version: '1.0'
proxy:
  manual:
    http_proxy:
      description: HTTP Proxy
      address:
        description: HTTP address
        type: domainname
        mandatory: true
      port:
        description: HTTP Port
        type: port
        default: '8080'

Les deux variables ont des types particuliers (domainname ou port) pour valider les valeurs configurer par l'utilisateur.

Pas la peine de préciser le type de la famille http_proxy parce qu'on a déclaré les sous-variables dans ce fichier.

Dupliquer la configuration HTTP vers HTTPS

On veux proposer à l'utilisateur la possiblité de renseigner le même proxy pour les requêtes HTTPS. Créons le fichier dict/04-proxy_manual_http_use_for_https.yml :

version: '1.0'
proxy:
  manual:
    use_for_https:
      description: Also use this proxy for HTTPS
      type: boolean

Cette variable est de type boolean. Sa valeur par défaut est True.

La configuration du proxy HTTPS

Ajoutons une nouvelle sous-famille ssl_proxy avec les variables address et port. Créons le fichier dict/05-proxy_manual_ssl_proxy.yml :

version: '1.0'
proxy:
  manual:
    ssl_proxy:
      description: HTTPS Proxy
      hidden:
        type: variable
        variable: rougail.proxy.manual.use_for_https
      address:
        description: HTTPS address
        type: domainname
        default:
          type: jinja
          jinja: |
            {% if rougail.proxy.manual.use_for_https %}
            {{ rougail.proxy.manual.http_proxy.address }}
            {% endif %}            
        mandatory: true
      port:
        description: HTTPS Port
        type: port
        default:
          type: jinja
          jinja: |
            {% if rougail.proxy.manual.use_for_https %}
            {{ rougail.proxy.manual.http_proxy.port }}
            {% endif %}            
        mandatory: true

Suivant la valeur de la variable rougail.proxy.mandatory.use_for_https cette famille apparaitra ou disparaitra (hidden). Contrairement à tout à l'heure, il n'est pas nécessaire de passer par une fonction Jinja.

De plus, la famille n'est pas désactivé (disabled) parce que les variables devront rester accessible en mode lecture seule.

Les variables address et port sont copiées de HTTP vers HTTPS si rougail.proxy.use_for_https est à True.

Testons différents configuration :

>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'No proxy'}

Pour le moment le proxy n'est pas configuré, donc on ne voit aucune variable.

Regardons ce qui se passe si on accède à l'option description rougail.proxy.manual si on n'est pas en mode manuel :

>>> pprint(config.option('rougail.proxy.manual').value.get(), sort_dicts=False)

On a bien une erreur (avec le message définit dans le template Jinja) :

tiramisu.error.PropertiesOptionError: cannot access to optiondescription "Manual proxy configuration" because has property "disabled" (the mode proxy is not manual)

Configurons le proxy en mode manuel :

>>> config.property.read_write()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> pprint(config.value.get(), sort_dicts=False)

Les variable apparaisse bien avec les valeurs voulues :

{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
 'rougail.proxy.manual.http_proxy.address': 'proxy.example',
 'rougail.proxy.manual.http_proxy.port': '8080',
 'rougail.proxy.manual.use_for_https': True}

Passons en mode lecture seule :

>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
 'rougail.proxy.manual.http_proxy.address': 'proxy.example',
 'rougail.proxy.manual.http_proxy.port': '8080',
 'rougail.proxy.manual.use_for_https': True,
 'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
 'rougail.proxy.manual.ssl_proxy.port': '8080'}

En mode lecture seule, on voit la configuration HTTPS qui apparait. De plus on peut remarquer également que les valeurs des variables de rougail.proxy.manual.http_proxy ont bien été copié dans rougail.proxy.manual.ssl_proxy.

En passant rougail.proxy.manual.use_for_https à False, il est possible de modifier la configuration HTTPS :

>>> config.property.read_write()
>>> config.option('rougail.proxy.manual.use_for_https').value.set(False)
>>> config.option('rougail.proxy.manual.ssl_proxy.address').value.set('other.proxy.example')
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
 'rougail.proxy.manual.http_proxy.address': 'proxy.example',
 'rougail.proxy.manual.http_proxy.port': '8080',
 'rougail.proxy.manual.use_for_https': False,
 'rougail.proxy.manual.ssl_proxy.address': 'other.proxy.example',
 'rougail.proxy.manual.ssl_proxy.port': '8080'}

La valeur de la variable rougail.proxy.manual.ssl_proxy.address a été modifiée. Mais si cette variable est à nouveau cachée, la valeur de cette variable reviens à la valeur par défaut :

>>> config.option('rougail.proxy.manual.use_for_https').value.set(False)
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
 'rougail.proxy.manual.http_proxy.address': 'proxy.example',
 'rougail.proxy.manual.http_proxy.port': '8080',
 'rougail.proxy.manual.use_for_https': False,
 'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
 'rougail.proxy.manual.ssl_proxy.port': '8080'}

La configuration du proxy SOCKS

Ajoutons une nouvelle sous-famille socks_proxy avec les variables address, port et version. Créons le fichier dict/06-proxy_manual_socks_proxy.yml :

version: '1.0'
proxy:
  manual:
    socks_proxy:
      description: SOCKS Proxy
      address:
        description: SOCKS Address
        type: domainname
      port:
        description: SOCKS Port
        type: port
      version:
        description: SOCKS host version used by proxy
        type: choice
        choices:
          - v4
          - v5
        default: v5

Rien a signaler pour cette famille et ces variables.

Le mode détection automatique

Ajoutons une nouvelle sous-variable auto. Créons le fichier dict/07-proxy_auto.yml :

version: '1.0'
proxy:
  auto:
    type: web_address
    description: Automatic proxy configuration URL
    mandatory: true
    disabled:
      type: jinja
      jinja: |
        {% if rougail.proxy.proxy_mode != 'Automatic proxy configuration URL' %}
        the proxy mode is not automatic
        {% endif %}        

Le type web_address impose une valeur qui commence par http:// ou https://. Cette variable est activée lorsque le proxy est en mode automatique.

Les exceptions au proxy

Enfin ajoutons une variable contenant les exceptions au proxy. Pour cela créons le fichier dict/07-proxy_no_proxy.yml :

version: '1.0'
proxy:
  no_proxy:
    description: Address for which proxy will be desactivated
    multi: true
    type: "domainname"
    params:
      allow_ip: true
      allow_cidr_network: true
      allow_without_dot: true
      allow_startswith_dot: true
    disabled:
      type: jinja
      jinja: |
        {% if rougail.proxy.proxy_mode == 'No proxy' %}
        proxy mode is no proxy
        {% endif %}        

C'est une variable de type domainname mais qu'on personnalise un peu (params), en effet on autorisé :

  • les IP
  • les réseaux au format CIDR
  • les noms de machine (donc sans '.')
  • les sous-domaines type .example

Il peut y avoir plusieurs exceptions au proxy, la variable est donc multi. Cette varible n'est pas accessible uniquement si aucun proxy n'est défini (disabled).

Pour tester :

>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_write()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> config.option('rougail.proxy.no_proxy').value.set(['.example', '192.168.1.1'])
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
 'rougail.proxy.manual.http_proxy.address': 'proxy.example',
 'rougail.proxy.manual.http_proxy.port': '8080',
 'rougail.proxy.manual.use_for_https': True,
 'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
 'rougail.proxy.manual.ssl_proxy.port': '8080',
 'rougail.proxy.manual.socks_proxy.address': None,
 'rougail.proxy.manual.socks_proxy.port': None,
 'rougail.proxy.manual.socks_proxy.version': 'v5',
 'rougail.proxy.no_proxy': ['.example', '192.168.1.1']}

Mais pas possible de mettre une valeur invalide :

>>> config.option('rougail.proxy.no_proxy').value.set(['.example', '192.168.1.1', 'not valid'])
[..]
tiramisu.error.ValueOptionError: "not valid" is an invalid domain name for "Address for which proxy will be desactivated", could be a IP, otherwise must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are allowed

La demande d'authentification

Rien de particulier à la création de la demande d'authentification. Pour cela créons le fichier dict/08-proxy_prompt_authentication.yml :

version: '1.0'
proxy:
  prompt_authentication:
    description: Prompt for authentication if password is saved
    type: boolean
    default: true
    disabled:
      type: jinja
      jinja: |
        {% if rougail.proxy.proxy_mode == 'No proxy' %}
        proxy mode is no proxy
        {% endif %}        

Le DNS du proxy SOCKS v5

La variable DNS pour le proxy SOCKS v5 n'apparait que si le proxy est configuré et que la version du proxy SOCKS sélectionné est bien la "v5".

Créons le fichier dict.09-proxy_proxy_dns_socks5.yml :

version: '1.0'
proxy:
  proxy_dns_socks5:
    description: Use proxy DNS when using SOCKS v5
    type: boolean
    default: false
    disabled:
      type: jinja
      params:
        socks_version:
          type: variable
          variable: rougail.proxy.manual.socks_proxy.version
          propertyerror: false
      jinja: |
        {% if rougail.proxy.proxy_mode == 'No proxy' %}
        the proxy mode is no proxy
        {% elif socks_version is undefined or socks_version == 'v4' %}
        socks version is v4
        {% endif %}        

La difficulté ici c'est que la variable rougail.proxy.manual.socks_proxy.version peut être désactivé (et donc non utilisable dans un calcul). Dans ce cas, nous allons ajouter un paramètre (ici appelé socks_version) qui contiendra, s'il n'y a pas d'erreur de propriété, la valeur de la variable. Sinon le paramètre ne sera pas passé au template Jinja. C'est pourquoi il faut tester dans le template Jinja si la variable socks_version existe bien.

Le DNS à travers HTTPS

Enfin nous allons configurer le DNS à travers HTTPS dans le fichier 10-proxy_dns_over_https.yml :

version: '1.0'
proxy:
  dns_over_https:
    description: DNS over HTTPS
    enable_dns_over_https:
      description: Enable DNS over HTTPS
      type: boolean
      default: false
    provider:
      description: Use Provider
      type: choice
      choices:
        - Cloudflare
        - NextDNS
        - Custom
      default: Cloudflare
      disabled:
        type: jinja
        jinja: |
          {% if not rougail.proxy.dns_over_https.enable_dns_over_https %}
          Enable DNS over HTTPS is False
          {% endif %}          
    custom_dns_url:
      description: Custom DNS URL
      type: web_address
      mandatory: true
      disabled:
        type: jinja
        params:
          provider:
            type: variable
            variable: rougail.proxy.dns_over_https.provider
            propertyerror: false
        jinja: |
          {% if provider is not defined or provider != 'Custom' %}  
          provider is not custom
          {% endif %}          
      validators:
        - type: jinja
          jinja: |
            {% if rougail.proxy.dns_over_https.custom_dns_url.startswith('http://') %}
            only https is allowed
            {% endif %}            

La seule particularitée ici est qu'on a ajouté une validation (validators) supplémentaire à la variable custom_dns_url. Seul une adresse commençant par https:// est autorisé (pas http://).

La configuration du proxy type FoxyProxy

Voici maintenant l'intégration d'une partie du plugin Firefox FoxyProxy.

L'idée est d'avoir un espace de nom spécifique à FoxyProxy et de retrouver dedans une partie du paramétrage qu'on aura fait dans l'espace de nom principal.

Voici à quoi ressemble la page :

FoxyProxy parameters page

Il est possible, dans ce plugin, de spécifié un nombre illimité de proxy. Notre famille "proxy" ne sera plus de type family comme tout a l'heure mais du type "leadership".

Voici le contenu complet de la configuration du proxy type FoxyProxy à mettre dans le fichier foxyproxy/00-base.yml :

---
version: '1.0'
proxy:
  _type: leadership
  title:
    description: Title or Description
    multi: true
  color:
    description: Color
    mandatory: true
  type:
    type: choice
    choices:
      - HTTP
      - HTTPS/SSL
      - SOCKS5
      - SOCKS4
      - PAC URL
      - WPAD
      - System (use system settings)
      - Direct (no proxy)
    default: Direct (no proxy)
  address:
    description: IP address, DNS name, server name
    multi: true
    mandatory: true
    disabled:
      type: jinja
      jinja: |
        {% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
        proxy does not need address
        {% endif %}        
    default:
      type: jinja
      params:
        firefox_address:
          type: variable
          variable: rougail.proxy.manual.http_proxy.address
          propertyerror: false
      jinja: |
        {% if firefox_address is not undefined %}
        {{ firefox_address }}
        {% endif %}        
  port:
    description: Port
    type: port
    mandatory: true
    default:
      type: jinja
      params:
        firefox_port:
          type: variable
          variable: rougail.proxy.manual.http_proxy.port
          propertyerror: false
      jinja: |
        {% if firefox_port is not undefined %}
        {{ firefox_port }}
        {% endif %}        
    disabled:
      type: jinja
      jinja: |
        {% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
        proxy does not need port
        {% endif %}        
  username:
    description: Username
    type: unix_user
    mandatory:
      type: jinja
      jinja: |
        {% if foxyproxy.proxy.password %}
        username is mandatory
        {% endif %}        
    disabled:
      type: jinja
      jinja: |
        {% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
        proxy does not need username
        {% endif %}        
  password:
    description: Password
    type: secret
    disabled:
      type: jinja
      jinja: |
        {% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
        proxy does not need password
        {% endif %}        
  url:
    type: web_address
    disabled:
      type: jinja
      jinja: |
        {% if foxyproxy.proxy.type not in ['PAC URL', 'WPAD'] %}
        proxy does not need url
        {% endif %}        

Quelques remarques :

  • dans la famille meneuse foxyproxy.proxy il y a une variable nommée "type", cela peut entrer en conflit avec l'attribute type. Dans ce cas, pour spécifier le type on utilise l'attribut _type
  • une variable suiveuse peut également être multiple (ce qui est le cas de foxyproxy.proxy.address)
  • foxyproxy.proxy.username devient obligatoire si foxyproxy.proxy.password est spécifié, en effet un mot de passe sans nom d'utilisateur n'a pas de sens

Testons :

>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> RougailConfig['extra_dictionaries']['foxyproxy'] = ['foxyproxy/']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> config.option('foxyproxy.proxy.title').value.set(['MyProxy'])
>>> config.option('foxyproxy.proxy.type', 0).value.set('HTTP')
>>> config.option('foxyproxy.proxy.color', 0).value.set('#00000')
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)

Ce qui donne :

{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
 'rougail.proxy.manual.http_proxy.address': 'proxy.example',
 'rougail.proxy.manual.http_proxy.port': '8080',
 'rougail.proxy.manual.use_for_https': True,
 'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
 'rougail.proxy.manual.ssl_proxy.port': '8080',
 'rougail.proxy.manual.socks_proxy.address': None,
 'rougail.proxy.manual.socks_proxy.port': None,
 'rougail.proxy.manual.socks_proxy.version': 'v5',
 'rougail.proxy.no_proxy': [],
 'rougail.proxy.proxy_dns_socks5': False,
 'rougail.proxy.dns_over_https.enable_dns_over_https': False,
 'foxyproxy.proxy.title': [{'foxyproxy.proxy.title': 'MyProxy',
                            'foxyproxy.proxy.color': '#00000',
                            'foxyproxy.proxy.type': 'HTTP',
                            'foxyproxy.proxy.address': ['proxy.example'],
                            'foxyproxy.proxy.port': '8080',
                            'foxyproxy.proxy.username': None,
                            'foxyproxy.proxy.password': None}]}

Le parti pris ici est de rendre obligatoire foxyproxy.proxy.username si un mot de passe est spécifié dans la variable foxyproxy.proxy.password.

Il est logique d'avoir un nom d'utilisateur sans mot de passe (dans ce cas là le mot de passe sera demander lors de la connexion au proxy). Mais l'inverse ne l'est pas.

D'un point de vu utilisateur cela peut paraitre perturbant (si on met le mot de passe, il faut revenir a l'option précédent pour préciser le mot de passe).

Il est possible d'inverser la logique. Si la variable foxyproxy.proxy.username est renseignée, la variable foxyproxy.proxy.password devient modifiable.

Aucune des deux variables n'a ainsi besoin d'être obligatoire.

Si vous préférez cette option, voici un second dictionnaire extra "foxyproxy/01-redefine.yml" qui va redéfinir le comportement uniquement des variable foxyproxy.proxy.username et foxyproxy.proxy.password :

---
version: '1.0'
proxy:
  username:
    redefine: true
    # suppress mandatory constrainte
    mandatory: false
  password:
    redefine: true
    hidden:
      type: jinja
      jinja: |
        {% if not foxyproxy.proxy.username %}
        no username defined
        {% endif %}        

A vous de jouer maintenant !