krules-py-cli 0.4.3
KRules Command Line
Requires Python: >=3.6

KRules CLI
==========
Krules CLI is a tool allow you to quickly create a project based on `KRules <https://github.com/airspot-dev/krules-core>`_ and organize your microservices in a hierarchical structure.

Initial steps
=============

Init project tree
~~~~~~~~~~~~~~~~~

To initialize a new **Project** run:

.. code:: bash

    krules-py project init your/project/path

| In case one or more directory of path does not exist, the script will create them for you.
| Moreover a *default* `profile <#managing-profiles>`__ will be generated.

To generate *default* profile some params must be prompted, but sometimes you need to avoid user interaction, maybe because you want integrate a CI/CD solution. This could be achieved using this optional params:

-  *--name [default current folder name]*: set project name;
-  -n --namespace [default default] : set in which namespace will be deployed your components;
-  -d --docker-registry. : set docker registry with which to push and deploy your image;
-  -i --image-base  : override ruleset image base.

Example

.. code:: bash

    krules-py project init . --name my-project

The output will be:

::

    . my-project
    │
    ├── base
    │   │
    │   ├── Dockerfile
    │   ├── ipython_config.py
    │   ├── Makefile
    │   ├── VERSION
    │   ├── app
    │   │   │
    │   │   ├── env.py
    │   │   └── app_functions
    │   │       │
    │   │       └── __init__.py
    │   └── k8s
    │       │
    │       ├── brokers.yaml
    │       ├── config-krules.yaml
    │       ├── config-krules-subjects-mongodb.yaml
    │       ├── config-krules-subjects-mongodb-auth.yaml
    │       ├── config-krules-subjects-redis.yaml
    │       ├── config-krules-subjects-redis-auth.yaml
    │       ├── event-display.yaml
    │       ├── kustomization.yaml
    │       └── triggers.yaml
    └── rulesets
        │
        └── patches.yaml


Config subject storage support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The next step is to choose the subject storage support.

The default storage class can be used just for local testing so you have to use another one in production environment, at now you can choose between **redis** and **mongodb**

Go to **base/app/env.py** to enable your preferred storage support.

First of all remove the default implementation:

.. code:: python

    from krules_core.tests.subject.sqlite_storage import SQLLiteSubjectStorage
    subject_storage_factory.override(
       providers.Factory(lambda x: SQLLiteSubjectStorage(x, ":memory:"))
    )

If, for example, you want to use redis support uncomment this code part:

.. code:: python

    # Redis subjects storage support
    subjects_redis_storage_settings = settings_factory() \
        .get("subjects-backends") \
        .get("redis")
    from redis_subjects_storage import storage_impl as redis_storage_impl

    subject_storage_factory.override(
        providers.Factory(
            lambda x: redis_storage_impl.SubjectsRedisStorage(x, subjects_redis_storage_settings.get("url"))
        )
    )

Then in **base/k8s** update ConfigMap and authorization data related to the storage support you choose.
Continuing with redis example.

**config-krules-subject-redis-auth.yaml**

.. code:: yaml

    apiVersion: v1
    data:
    password: cGFzc3dvcmQ= # replace with your redis password in base64 format
    kind: Secret
    metadata:
      name: config-krules-subjects-redis-auth
    type: Opaque

**config-krules-subject-redis.yaml**

.. code:: yaml

   apiVersion: v1
   kind: ConfigMap
   metadata:
     name: config-krules-subjects-redis
   data:
     config_subjects_redis.yaml: |
       url: redis://:${KRULES_SUBJECTS_REDIS_PASSWORD}@redis.address/0 # replace with your redis address


Remember to include them in the **kustomization.yaml**.

The last step is to make those configurations known to your rulesets.

In **rulesets/patches.yaml** uncomment the `patch <#how-to-use>`__ related to your chosen storage support.

.. code:: yaml

    ## Redis subjects backend
    kinds:
      - serving.knative.dev/v1beta1:Service
      - apps/v1:Deployment
    labelsMatch:
      - airspot.krules.dev/type: ^ruleset$
    patch:
      spec:
        template:
          spec:
            containers:
              - patch__0:
                env:
                  - name: KRULES_SUBJECTS_REDIS_PASSWORD
                    valueFrom:
                      secretKeyRef:
                        name: config-krules-subjects-redis-auth
                        key: password
                volumeMounts:
                  - name: config-krules-subjects-redis-volume
                    mountPath: /krules/config/subjects-backends/redis

          volumes:
            - name: config-krules-subjects-redis-volume
              configMap:
                name: config-krules-subjects-redis

Build and push base image
~~~~~~~~~~~~~~~~~~~~~~~~~

Go to **base** folder and run *make*.

**make** command will take care of each needed operation to build and push your base image correctly. You can also run each step individually:

- **VERSION**: Detect changes checking your Dockerfile and each your .py file in the **base/app** folder and possibly build a new base image version;
- **push**: If there is a new image build in your local registry push it to your Docker repository;
- .lastResources: Apply all yaml in **k8s** folder to your namespace.
- clean: Clean all files generated by **make** command, useful to relaunch it without make any changes to your files.

Working with Rulesets
----------------------

Init Ruleset tree
~~~~~~~~~~~~~~~~~~

To create a new **Ruleset** go to **rulesets** folder and run:

.. code:: bash

    krules-py ruleset create my-ruleset

The output will be:

::

    . my-ruleset
    │
    ├── Dockerfile.origin
    ├── Makefile
    ├── VERSION
    ├── app
    │   │
    │   └── rules.py
    └── k8s
        │
        ├── kustomization.yaml
        └── service.yaml

Deploy your Ruleset
~~~~~~~~~~~~~~~~~~~

To deploy your code on Kubernetes got to your ruleset folder and run
*make*. *make* command will take care of each needed operation to deploy your ruleset correctly detecting files changes.

You can also run each step individually:

- **Dockerfile**: Detect changes in your Dockerfile.origin and generate a new Dockerfile if needed.
So, if you want to change the Dockerfile don't modify directly it but edit the *Dockerfile.origin*;
- **VERSION**: Detect changes checking your Dockerfile and each your .py file in the rulesset **app** folder and possibly build a new rulesset image version;
- **push**: If there is a new image build in your local registry push it to your Docker repository;
- .lastResources: Apply all yaml in **k8s** folder to your namespace.
- clean: Clean all files generated by **make** command, useful to relaunch it without make any changes to your files.

Each ruleset inherit your project image base, so if you change it you have to redeploy your ruleset using:

.. code:: bash

    make clean && make

Generate your Ruleset manifest (using kustomize)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The KRules CLI integrate kubectl
`kustomize <https://github.com/kubernetes-sigs/kustomize>`_.
command. In added to the kustomize standard functions, it provide to the
user another patches logic.

Usage:

.. code:: bash

    krules-py ruleset patch

*patch* command apply each **patches.yaml** file from the project root
to the ruleset folder, so it is possible to define some patches shared
by all rulesets or by group of ruleset.

Example:

::

    . rulesets
    │
    ├── patches.yaml
    ├── my-group
    │   │
    │   ├── my-ruleset-2/
    │   ├── my-ruleset-3/
    │   ├── my-ruleset-4/
    │   └── patches.yaml
    └── my-ruleset-1
        │
        ...
        ├── kustomization.yaml
        └── service.yaml

*rulesets/patches.yaml* will be applied to all rulesets, while
*rulesets/my-group/patches.yaml* will modified just the rulesets
inside *my-group* folder

Custom path
~~~~~~~~~~~

*patch* command search for kustomization file in the current folder or
in k8s one. If you put your file in another path you can use the
**path** parameter

Usage:

.. code:: bash

    krules-py ruleset patch -p [--path] custom/path

Warning
-------

*path* param refer to folder containing the kustomization file not to
the file itself.

How to use
~~~~~~~~~~

*Basic Usage*

Supposing you define this 3 files:

**service.yaml**

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      template:
        spec:
          contatiners:
          - name: my-container
    ---

**triggers/my-trigger.yaml**

.. code:: yaml

    apiVersion: eventing.knative.dev/v1alpha1
    kind: Trigger
    metadata:
      name: my-trigger

**kustomization.yaml**

.. code:: yaml

    commonLabels:
      airspot.krules.dev/ruleset: my-ruleset
    resources:
    - service.yaml
    - triggers/my-trigger.yaml

You can override the previous components creating a file named
*patches.yaml* in some of parent folders

.. code:: yaml

    kinds:
      - v1:Service
      - serving.knative.dev/v1alpha1:Service
    patch:
      metadata:
        labels:
          app: my-app
    ---
    kinds:
      - serving.knative.dev/v1alpha1:Trigger
    patch:
      spec:
        broker: my-broker

**kinds** : list of kinds of components will be affected from the patch.
Format: : **patch** : list of field will be modify with this patch
**labelsMatch**: list of regex to filter components by metadata labels.

The output will be:

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
      labels:
        airspot.krules.dev/ruleset: my-ruleset
        app: my-app
    spec:
      template:
        spec:
          contatiners:
          - name: my-container

    ---
    apiVersion: eventing.knative.dev/v1alpha1
    kind: Trigger
    metadata:
      name: my-trigger
      labels:
        airspot.krules.dev/ruleset: my-ruleset
    spec:
      broker: my-broker
    ---

*Adding a second container*

To add element to a list just define the new elements in the patch
inside the list key.

**service.yaml**

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      template:
        spec:
          contatiners:
          - name: my-first-container
    ---

**patches.yaml**

.. code:: yaml

    kinds:
      - v1:Service
      - serving.knative.dev/v1alpha1:Service
    patch:
      spec:
        template:
          spec:
            containers:
              - name: my-second-container

Output:

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      template:
        spec:
          contatiners:
          - name: my-first-container
          - name: my-second-container
    ---

*Adding volumeMount just to a specific container*

To override a single list element use **patch\__<index>** keyword, where
*index* indicates the index of the element of the list to be
modified.

**service.yaml**

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      template:
        spec:
          contatiners:
          - name: my-first-container
          - name: my-second-container
          - name: my-third-container
    ---

**patches.yaml**

.. code:: yaml

    kinds:
      - v1:Service
      - serving.knative.dev/v1alpha1:Service
    labelsMatch:
      - app: my-app
    patch:
      spec:
        template:
          spec:
            containers:
              - patch__1:
                  volumeMounts:
                    - name: my-volume
                      mountPath: /configs/

            volumes:
              - name: my-volume
                configMap:
                  name: my-config

Output:

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      template:
        spec:
          contatiners:
            - name: my-first-container
            - name: my-second-container
              volumeMounts:
                    - name: my-volume
                      mountPath: /configs/
            - name: my-third-container
          volumes:
            - name: my-volume
              configMap:
                name: my-config
    ---

*Update a specific container image*

**service.yaml**

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      template:
        spec:
          containers:
          - name: my-container
          - name: my-other-container
    ---

.. code:: bash

    krules-py ruleset patch set-image my-container=my-docker-registry/my-image:latest

Output:

**service.yaml**

.. code:: yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      template:
        spec:
          containers:
          - name: my-container
            image: my-docker-registry/my-image:latest
          - name: my-other-container
    ---

Custom ruleset template
~~~~~~~~~~~~~~~~~~~~~~~

It is possible that default ruleset template does not completely
satisfy your requirements. You can override it using:

.. code:: bash

    krules-py gen-rs-template your_template_dir [--set-default]

The selected template directory must be contained in your project. \*\*
--set-default \*\* flag set your directory as the default one for each
ruleset. The output will be:

::

    . your_template_dir
    │
    ├── Dockerfile.origin
    ├── Makefile
    ├── VERSION
    ├── app
    │    │
    │    └── rules.py
    └── k8s
         │
         ├── kustomization.yaml
         ├── service.yaml
         └── triggers.yaml

To reset default rulesets template folder use:

.. code:: bash

    krules-py gen-rs-template --unset-default

Managing Version
----------------

Both base and rulesets folders contains a file **VERSION**.
This file contains your current local image version.
When you run make in a ruleset folder your local image will be deployed only if its version is greater than the cluster one.

Adding triggers
~~~~~~~~~~~~~~~

To work properly a rule must be registered to a trigger.
To create a new trigger run

.. code:: bash

    krules-py trigger create my-trigger

Output:

**triggers.yaml**

.. code:: yaml

    apiVersion: eventing.knative.dev/v1beta1
    kind: Trigger
    metadata:
      name: my-trigger
    spec:
      filter:
        attributes:
          type: my-ruleset-my-trigger
    subscriber:
      ref:
        apiVersion: v1
        kind: Service
        name: my-ruleset
    ---

This command updates the file **triggers.yaml** located in k8s folder. If the file does not exist a new one will be created.

You could have some particular requirements for your trigger.

If, for example, you want to add a filter for a certain type run:

.. code:: bash

    krules-py trigger create my-type-trigger --type my-type

Output:

**triggers.yaml**

.. code:: yaml

    apiVersion: eventing.knative.dev/v1beta1
    kind: Trigger
    metadata:
      name: my-type-trigger
    spec:
      filter:
        attributes:
          type: my-type
    subscriber:
      ref:
        apiVersion: v1
        kind: Service
        name: my-ruleset
    ---

Another interesting feature could be raise a trigger when a property is changed. To achieve this run:

.. code:: bash

    krules-py trigger create my-property-trigger --on-propchange my-property


Output:

**triggers.yaml**

.. code:: yaml

    apiVersion: eventing.knative.dev/v1beta1
    kind: Trigger
    metadata:
      name: my-property-trigger
    spec:
      filter:
        attributes:
          propertyname: my-property
          type: subject-property-changed
    subscriber:
      ref:
        apiVersion: v1
        kind: Service
        name: my-ruleset
    ---

With CLI you can also create a trigger with your custom attributes running:

.. code:: bash

    krules-py trigger create my-custom-trigger --attrs key1=val1 key2=val2


Output:

**triggers.yaml**

.. code:: yaml

    apiVersion: eventing.knative.dev/v1beta1
    kind: Trigger
    metadata:
      name: my-custom-trigger
    spec:
      filter:
        attributes:
          key1: val1
          key2: val2
    subscriber:
      ref:
        apiVersion: v1
        kind: Service
        name: my-ruleset
    ---

Managing profiles
~~~~~~~~~~~~~~~~~


The KRules CLI provides also a profiles handler. A profile contains all
useful defaults value as docker registry and kubernetes namespace.

Add new profile
---------------

.. code:: bash

    krules-py profile add myProfile

**Optional arguments**

-  --set-default: set the new profile as the default one;
-  -ns --namespace: set profile namespace;
-  -d --docker-registry: set profile Docker registry.

Set/Get default profile
-----------------------

.. code:: bash

    $ krules-py profile set myProfile
    $
    $ krules-py profile get
    $ myProfile

To list all available profiles run

.. code:: bash

    krule-py profile list

Set/Get profile value
---------------------

.. code:: bash

    $ krule-py profile set-value namespace myNamespace
    $
    $ krule-py profile get-value namespace
    $ myNamespace

To print all profile values run

.. code:: bash

    krule-py profile values