From 1e3e26da43a42fd74a30b03445cae10020f35883 Mon Sep 17 00:00:00 2001 From: arthur91f Date: Wed, 20 Mar 2024 19:41:23 +0100 Subject: [PATCH 1/8] docs: rewrite readme.md --- README.md | 133 +++++++++++++++++++++++++----------------------------- 1 file changed, 61 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 6412df8..b4ec43e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Exeiac -## Description +## Why use exeIaC `exeiac` is a tool that enables infrastructure folks to handle several different provisionning IaC (Infrastructure as Code) tools, under a single CLI, and helps solving some recurrent paintpoints with IaC. @@ -21,84 +21,73 @@ This project was born from the following needs: even if your infrastructure management tool doesn't provide that feature -## DOCUMENTATION +## What is a brick +First, exeIaC deals with infra bricks. What is an infra brick ? Basically +it's a directory that contains some infra code. It can be a terraform directory +to deploy a VM, an ansible playbook to configure an host, an helm chart or simply +a template that describe some instruction to do manually. -- philosophy: theoretical approach useful to write your infra code and understand - exeiac best practices - - common infra code problems we try to solve with exeiac - - define infra as a set of bricks - - define the brick concept - - explain how bricks depends of each other - - vocabulary -- development: contain specs and schema to understand how it is coded -- user: contain all you need to use the tool and create an infra code that - respect the convention and best practices -- examples: examples of simple infra code and module +To understand this page it's sufficient. But if you want to understand deeper +the genious idea behind that concept, how many type of dependencies exist +between your bricks and all element that compose your brick (code description, +input, ouput, events, trigger, command), I let you read that [page](./docs/brick_concept_and_dependencies) -## Get started +## Get started and how it works summary -### Installation +- **1. Get exeiac binary** + ``` bash + go install github.com/arthur91f/exeiac/src/exeiac/src/exeiac@main + ``` -Clone the git repository and build: -``` bash -$ git clone github.com/arthur91f/exeiac/src/exeiac -$ cd exeiac -$ go install src/exeiac -``` +- **2. Write your modules** in whatever language you want. A module can be seen + as a makfile to deploy your brick. Basically it's a shell script that follow some + conventions describe here : [How to write module](./docs/howto_write_module.md) + You have to implement three command: + - *describe_module_for_exeiac* (that display a json) + - *lay* to deploy your brick + - *output* to display some specs of your brick as ip address, login... + - ... you can implement other command as plan, remove, lint, help... + +- **3. Put a yaml file in each IaC directory** to describe your bricks as here + [How to write brick](./docs/howto_write_brick.md). It will let: + - exeiac identify your IaC directory as an infra brick + - associate your brick to its module + - exeiac understand your brick's dependencies and how to present it to the brick -There is no release process yet, but on Go version 1.16 or later you can: -``` bash -# Install at tree head: -$ go install github.com/arthur91f/exeiac/src/exeiac/src/exeiac@main -``` +- **4. Write your exeiac conf file** in your home or in /etc to let exeiac binary + find your module and your infra code. + [How to write config file](./docs/howto_write_configuration_file.md) -- get the exeiac binary -- have an infra code that follow some conventions (see below for more details) - - each brick is a directory prefixed by a number to make the apply order - transparent - - each elementary brick should have a brick.yml to define how it will be - executed and the input it needed from dependencies - - each elementary brick should reference in brick.yml an executable or module - to execute itself. (a module is simply an executable that is not in the - brick directory and that can be called by many bricks) - - Read [howto_write_brick](./docs/howto_write_brick.md) -- create a conf file in /etc/exeiac/exeiac.yml or $HOME/.config/exeiac.yml - ```yaml - modules_path: - terraform: $HOME/git-repos/exeiac-modules/terraform - ansible: $HOME/git-repos/exeiac-modules/terraform - room_paths_list: - - $HOME/git-repos/infra-ground - - $HOME/git-repos/applications - - $HOME/git-repos/users - ``` +- **5. Enjoy exeiac** + Here some example of basic command you can execute + - Display output of a brick + ```bash + exeiac output infra-ground/envs/staging/network + ``` + - Plan all sub-brick of infra-ground/envs/staging + ```bash + exeiac output infra-ground/envs/staging + ``` + - Display all bricks should be re-deploy after the change of the brick's network + output .network.ip_range + ```bash + exeiac get-depends infra-ground/envs/staging/network -j $.network.ip_range + ``` + - Display all bricks that can be impacted by the re-deploy of the birck network + ```bash + exeiac show infra-ground/envs/staging/network --bricks-specifiers linked_next --format name + ``` + - Deploy/re-deploy a drift in brick staging/network and all other bicks + impacted by that drift recursively. + ```bash + exeiac smart-lay infra-ground/envs/staging/network --non-interactive -### Simple command line examples -- display a brick output - ```json - $ exeiac output ./infra-core/2-staging/2-ssh_bastion - { - "instance_id": "bastion-staging-221022", - "private_ip": "10.11.3.2", - "public_ip": "34.33.31.30" - } - ``` -- deploy a brick and recursively deploy all bricks that depends on an output - that have changed. Note that here we have used the brickname and not the path - ```bash - exeiac lay infra-core/staging/ssh_bastion --bricks-specifier=selected+needed_dependents - ``` -- destroy a higher level brick. It will destroy all elementary bricks - contained in the higher level bricks in the right order. - ```bash - exeiac remove infra-core/staging - ``` -- get more help - ```bash - exeiac help - ``` +## Useful links -### Create a module or an executable +- **local** + - [How to join us](./docs/to_write.md) +- **externe** + - [Download exeiac](https://download-exeiac.91f.ovh) + - [exeIaC presentation](https://drive.google.com/blabla) -Read [howto_write_module](./docs/howto_write_module.md) From 34f70b0731ff685accbcf2821aff1a7b2de6f86f Mon Sep 17 00:00:00 2001 From: arthur91f Date: Wed, 20 Mar 2024 19:41:58 +0100 Subject: [PATCH 2/8] docs: sav work to reset --- docs/howto_write_configuration_file.md | 60 ++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 docs/howto_write_configuration_file.md diff --git a/docs/howto_write_configuration_file.md b/docs/howto_write_configuration_file.md new file mode 100644 index 0000000..a982a33 --- /dev/null +++ b/docs/howto_write_configuration_file.md @@ -0,0 +1,60 @@ +# Write the exeiac configuration file + +## Generality + +The exeIaC configuration file is used to by the exeiac binary to locate your +modules and rooms (repositories that contains your brick). + +It is also used to pass some default arguments. + +The configuration file is quite critical. Indeed, some information inside are +sticky to your host some others are sticky to your infra code. +(where do you have download your infra code ?) + (the module and room name, the action that call +) + +if you change the room's name +or the module's name, your + +## Where to write it ? + +You can write the file in your home : $HOME/.config/exeiac/exeiac.yml +More generally, exeiac use the golang xdg library function xdg.SearchConfigFile + to find the configuration file. + +You can also pass the configuration file with an option: +```bash +exeiac -c ./exeiac-conf.yml list-bricks +exeiac --configuration-file ./exeiac-conf.yml list-bricks +``` +In case the documenation is not up to date try exeiac help ;) + +## How to write it ? + +Use the yaml format, + + +## An example +```yaml +modules: + - name: ansible + path: /home/arthur91f/git/infra-library/exeiac-module-ansible.sh + - name: terraform + path: /home/arthur91f/git/infra-library/exeiac-module-terraform.py +rooms: + - name: ground + path: /home/arthur91f/git/infra-ground/environment + - name: applicationA + path: /home/arthur91f/git/applicationA + - name: user-access + path: /home/arthur91f/git/infra-user-access +default_arguments: + other_options: [] # useful if you don't want to always pass the sae option as --format=name or --non-interactive + + # Will ask input of + default_is_input_needed: false # By default exeiac won't search the inputs of a brick it's not usefull for action init for example + inputs_needed_for: # will search the inputs for all those action + - plan + - lay + - remove +``` From e2d300115ac39e0a8509c9706b890111ff053e9d Mon Sep 17 00:00:00 2001 From: arthur91f <53146626+arthur91f@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:02:10 +0100 Subject: [PATCH 3/8] docs(howto_write_conf_file): updates --- docs/howto_write_configuration_file.md | 153 +++++++++++++++---------- 1 file changed, 93 insertions(+), 60 deletions(-) diff --git a/docs/howto_write_configuration_file.md b/docs/howto_write_configuration_file.md index a982a33..b4ec43e 100644 --- a/docs/howto_write_configuration_file.md +++ b/docs/howto_write_configuration_file.md @@ -1,60 +1,93 @@ -# Write the exeiac configuration file - -## Generality - -The exeIaC configuration file is used to by the exeiac binary to locate your -modules and rooms (repositories that contains your brick). - -It is also used to pass some default arguments. - -The configuration file is quite critical. Indeed, some information inside are -sticky to your host some others are sticky to your infra code. -(where do you have download your infra code ?) - (the module and room name, the action that call -) - -if you change the room's name -or the module's name, your - -## Where to write it ? - -You can write the file in your home : $HOME/.config/exeiac/exeiac.yml -More generally, exeiac use the golang xdg library function xdg.SearchConfigFile - to find the configuration file. - -You can also pass the configuration file with an option: -```bash -exeiac -c ./exeiac-conf.yml list-bricks -exeiac --configuration-file ./exeiac-conf.yml list-bricks -``` -In case the documenation is not up to date try exeiac help ;) - -## How to write it ? - -Use the yaml format, - - -## An example -```yaml -modules: - - name: ansible - path: /home/arthur91f/git/infra-library/exeiac-module-ansible.sh - - name: terraform - path: /home/arthur91f/git/infra-library/exeiac-module-terraform.py -rooms: - - name: ground - path: /home/arthur91f/git/infra-ground/environment - - name: applicationA - path: /home/arthur91f/git/applicationA - - name: user-access - path: /home/arthur91f/git/infra-user-access -default_arguments: - other_options: [] # useful if you don't want to always pass the sae option as --format=name or --non-interactive - - # Will ask input of - default_is_input_needed: false # By default exeiac won't search the inputs of a brick it's not usefull for action init for example - inputs_needed_for: # will search the inputs for all those action - - plan - - lay - - remove -``` +# Exeiac + +## Why use exeIaC +`exeiac` is a tool that enables infrastructure folks to handle several +different provisionning IaC (Infrastructure as Code) tools, under a +single CLI, and helps solving some recurrent paintpoints with IaC. + +It follows the brick convention, which describes an infrastructure as +a set of bricks. A brick is a piece of infrastructure, it +simultaneously is the actual infrastructure element, the code that +describes it and a piece code that allows its execution; be it a +terraform state, an ansible playbook or a helm chart for instance. + +This project was born from the following needs: +- solve dependencies issues +- increase transparency as to how a piece of infrastructure should be + deployed. No matter the provisionning tool you use, if there's a + module for it, `exeiac` will handle it +- allow for a clean way to interact with only a part of your + infrastructure in a safe way, without breaking the dependency tree, + even if your infrastructure management tool doesn't provide that + feature + +## What is a brick +First, exeIaC deals with infra bricks. What is an infra brick ? Basically +it's a directory that contains some infra code. It can be a terraform directory +to deploy a VM, an ansible playbook to configure an host, an helm chart or simply +a template that describe some instruction to do manually. + +To understand this page it's sufficient. But if you want to understand deeper +the genious idea behind that concept, how many type of dependencies exist +between your bricks and all element that compose your brick (code description, +input, ouput, events, trigger, command), I let you read that [page](./docs/brick_concept_and_dependencies) + +## Get started and how it works summary + +- **1. Get exeiac binary** + ``` bash + go install github.com/arthur91f/exeiac/src/exeiac/src/exeiac@main + ``` + +- **2. Write your modules** in whatever language you want. A module can be seen + as a makfile to deploy your brick. Basically it's a shell script that follow some + conventions describe here : [How to write module](./docs/howto_write_module.md) + You have to implement three command: + - *describe_module_for_exeiac* (that display a json) + - *lay* to deploy your brick + - *output* to display some specs of your brick as ip address, login... + - ... you can implement other command as plan, remove, lint, help... + +- **3. Put a yaml file in each IaC directory** to describe your bricks as here + [How to write brick](./docs/howto_write_brick.md). It will let: + - exeiac identify your IaC directory as an infra brick + - associate your brick to its module + - exeiac understand your brick's dependencies and how to present it to the brick + +- **4. Write your exeiac conf file** in your home or in /etc to let exeiac binary + find your module and your infra code. + [How to write config file](./docs/howto_write_configuration_file.md) + +- **5. Enjoy exeiac** + Here some example of basic command you can execute + - Display output of a brick + ```bash + exeiac output infra-ground/envs/staging/network + ``` + - Plan all sub-brick of infra-ground/envs/staging + ```bash + exeiac output infra-ground/envs/staging + ``` + - Display all bricks should be re-deploy after the change of the brick's network + output .network.ip_range + ```bash + exeiac get-depends infra-ground/envs/staging/network -j $.network.ip_range + ``` + - Display all bricks that can be impacted by the re-deploy of the birck network + ```bash + exeiac show infra-ground/envs/staging/network --bricks-specifiers linked_next --format name + ``` + - Deploy/re-deploy a drift in brick staging/network and all other bicks + impacted by that drift recursively. + ```bash + exeiac smart-lay infra-ground/envs/staging/network --non-interactive + + +## Useful links + +- **local** + - [How to join us](./docs/to_write.md) +- **externe** + - [Download exeiac](https://download-exeiac.91f.ovh) + - [exeIaC presentation](https://drive.google.com/blabla) + From 24b3047ab08f56c283182a39a9027dda36a80367 Mon Sep 17 00:00:00 2001 From: arthur91f <53146626+arthur91f@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:11:10 +0100 Subject: [PATCH 4/8] docs(howto_write_brick): updates --- docs/howto_write_brick.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/howto_write_brick.md b/docs/howto_write_brick.md index 38ee001..6c62c49 100644 --- a/docs/howto_write_brick.md +++ b/docs/howto_write_brick.md @@ -28,7 +28,8 @@ brick.yml file that describe its module, inputs, triggers ... ## Directory convention You can identify brick from their path: -- A brick must be in a "room" (listed in exeiac conf files) or +- A brick must be in a "room" (listed in + [exeiac configuration file](./howto_write_configuration_file.md)) or be a room. - A brick directory name must begin with a priority number that describe the building sequence of bricks inside the same room or super brick. From 4a02f7b8af9c5aeef8ac37b0cc6a1146752f2505 Mon Sep 17 00:00:00 2001 From: arthur91f Date: Fri, 29 Mar 2024 09:57:02 +0100 Subject: [PATCH 5/8] docs: brick concept and deps --- docs/brick_concept_and_dependencies.md | 224 +++++++++++++++++++++++++ docs/brick_elements.drawio.png | Bin 0 -> 35462 bytes 2 files changed, 224 insertions(+) create mode 100644 docs/brick_concept_and_dependencies.md create mode 100644 docs/brick_elements.drawio.png diff --git a/docs/brick_concept_and_dependencies.md b/docs/brick_concept_and_dependencies.md new file mode 100644 index 0000000..ac4f11c --- /dev/null +++ b/docs/brick_concept_and_dependencies.md @@ -0,0 +1,224 @@ +# Brick concept and dependencies + +This docs will help you to understand how to represent a brick and its +sub-element, understand how playing with these element will let us manage the 3 dependency types. + +To understand the why of this concept you should read this article: [philosophy]( +./philosophy.md) + +## Define brick concept + +### In short words + +First, exeIaC deals with infra bricks. What is an infra brick ? Basically it's +a directory that contains some infra code. It can be a terraform directory to +deploy a VM, an ansible playbook to configure an host, an helm chart or simply +a template that describe some instruction to do manually. + +### Try to go deeper + +To manage our infra, exeIaC only manage brick (it's far more simple than have +too much concept). So an infra have to be seen as one super brick that contains +bricks, that can contains bricks. + +Bricks are layed on other bricks and so depends of brick that have +been layed previously. Where are thoose dependencies ? We have only one concept +so those dependency are in bricks too. + +In a devops way of thinking we want to have the most comprehensive +approach of infra. So the infra should contain all elements and process to +deploy and maintain an application on line. So a brick is not only the +terraform code it is also the makefile or the documentation to deploy it (and +not only deploy it but also plan, lint...) + +If we write the last 2 ingredients in our brick we should be able to have the +intelligence to deploy our infra in the right order. But it still miss +something. An infra lives and sometime you update someting. And as in a wall, +you can't change a brick in a middle of the wall without update brick on that +changed brick, in infra it's the same. And we may have something like events +that triggers a re-deploy. + +So a brick contains : +- Code description: the infra code (terraform, ansible...) +- Commands: the brick interface to let us know how to deploy, plan, get the + specific state of a brick... +- Output: the infra specific state as the password generated or the ip address + assigned during the deploy of the brick +- State: the infra real state : does the infra drifted or have already been + deployed +- Input: Some data of its environment (other brick output) taken in input of + some command +- Trigger: some trigger to know when it should be re-deploy +- Event: that will trigger other bricks command. + +So brick's input and output are static. They describe a state of the infra. +Event and trigger are ephemeral they describe what happens and what should be +done. + +![](./brick_elements.drawio.png) + +So command can be execute by human after modifying code or can be triggered by +an event of an other brick. + +Command takes in input some output of other bricks and the code description. + +Command also generate as output some events. Those events could be : +- the deploy correct a drift or have nothing to do +- the deploy have changed this output value +- the deploy have recreated this VM instance (because the specs of the VM + you can find in output haven't change so it's not visible by watching outputs) + + +## How exeIaC implement it + +For exeIaC the brick command is a module. It permit factorization. And basically it's +an executable with a standardized interface that can be enriched. +The module interface should contain a method to specify what is the implemented interface and how to retrieve events for each method. +[How to write module](./howto_write_module.md) + +What input is needed for what command method and how to present it is defined +in a brick.yml file inside the brick directory. +[How to write a brick](./howto_write_brick.md) + + +## Dependency types + +### Examples + +To explore all the three dependency types, we will base us on an example. In this example we will focus on the link between 3 brick of an infra. +Here the brick list and their outputs : +- A: a cloud VM instance + - output: + - hw_spec.ram + - hw_spec.cpu + - event: + - vm_recreated +- B: configuration of that VM instance + - input: + - hw_spec.ram +- C: a hop ssh server that is needed to connect and configure the instance + - output: + - ip_address + - credentials + +The list of outputs are not exhaustive in a real case but we only need +those to explain the three type of dependency. + +`schema` + + +### Precise what is a dependency + +Ok brick depends of each other and of course we can't create a VM on a network +that doesn't exist or configure a VM that doesn't exist. But in what way define +different type of dependencies can be usefull ? + +We search to define different type of dependency to know when we have to +re-deploy a brick. It's not because a brick has changed that you have to +re-deploy all bricks on top of it. If I install vim on my hop ssh server, I +don't need to redeploy because it's not a dependency. + +So a dependency is a data from brick's output or event. A dependency can be an +input or a trigger or perhaps both. + +But we call that dependency, what brick element depends of a dependency in an +implementation point of view ? It's the command element (module) that need +dependencies to execute itself. + +All command method needs input ? Maybe but not the same. If you implement a +lint method to check your code I doubt that it needs any input. Same, maybe +for a terraform apply you need to have a tfvars file, but to make a terraform +out you only need acces to the state. + +So a dependency is a data that you take from an other brick output or event and +that you need for some specific module method. + +And each dependency can trigger or not a specific brick's module method. + +### The classic dependency + +This is the most common : when the input changes, it triggers a redeploy. +For example for brick B it could be: A.hw_specs.ram to reconfigure the java +heapsize. + +### The weak dependency + +The module need the data as input to excute, but the change of the data won't +trigger a re-deploy. For example to deploy the brick B you need to get +C.ip_address and C.credentials to connect to VM but actually, if C.credentials +change it won't impact the brick B configuration. + +### The special dependency + +This category contains all dependency that trigger a re-deploy but are not an +input change. +For example, if you want to destroy and recreate with the same input the VM of +brick A or even you destroy and recreate it without changing any output readed +as input of brick B. You need to re-deploy the configuration of the VM (brick +B). Actually the configuration has disapear. +You can do that by testing the boolean A.vm_recreated. + +Else, brick A could have as output a vm-id or a creation-timestamp that could be +manage as a classic dependency. But you see here that this dependency value +isn't needed as input. + +We can also imagine that brick A create lot of VM so it's event could be a list +of recreated VMs. So the trigger would be a test on a data type list. + +As you see we can imagine many special dependency. For exeIaC it's only a test +that is not linked to a change of a value. + +### What other thing the system permit + +We can trigger other action than a simple deploy (lay). It is usefull if you +want to implement many different lay function. For example for 1-10-100 deploy +or for renewing password. + + +## From elementary brick to infra + +### Elementary brick + +An elementary brick is a brick that contain infra code. It's the smallest +element of an infra. What we have define in [Sub-element of a brick](#sub-element-of-a-brick) is actually an elementary brick. + +### Higher order brick + +An higher order brick only contains some elementary bricks. +To execute an higher-order brick you have to execute in the right order all +sub-bricks. The input of an higher order brick correspond to the inputs of +all the sub-brick (without input that call output of the higher-order brick). +The output of an higher order brick correspond to all the output of elementary + bricks it contains. + +An higher order brick is not seen very differently as an elementary brick. +Sometimes exeIaC vision is only based on elementary bricks but lets re-define +brick's element for an higher order brick +- **input & triggers**: all inputs and triggers of it's sub-brick minus inputs + and events that reference other sub-bricks +- **output & events**: all outputs and events of its sub-bricks +- **command**: deploy an higher order brick is deploy all it's sub-brick in the + right order. + +**NB: execution order** to rework +As you can see in page [how to write a brick](./howto_write_brick.md) each +brick has its directory name prefixed by a priority number. So take care +of your higher-order brick composition. Actually in a higher-order brick you +will execute all brick with the priority you see. A higher order brick will +be seen as an elementary brick. So it is simple for a human to read his/her +infra code. You won't have to understand how to execute a brick to execute it. + +### Room + +It's an higher order brick that correspond to a directory and that have to be +listed in the [exeIaC configuration file](./howto_write_configuration_file.md) +to be seen by exeIaC. It's convention are a bit different than other brick, but +it can be considered as a brick. + +Usually it is a git repository. + +### Infra + +An infra is an ordered list of rooms. So it's also a brick. A self sufficient +brick with no input, and triggers and with no output and events. + diff --git a/docs/brick_elements.drawio.png b/docs/brick_elements.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..24b7421bc94d22e2ba4ef08d81b861f2e1abad2b GIT binary patch literal 35462 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 VNniQ3a}fg&c)I$ztaD0e0ssxI?V Date: Mon, 1 Apr 2024 15:37:21 +0200 Subject: [PATCH 6/8] docs: brick concept and deps improve --- docs/brick_concept_and_dependencies.md | 323 +++++++++--------- .../brick_elements.drawio.png | Bin .../brick_elements_dependency.drawio.png | Bin 0 -> 35485 bytes docs/illustrations/infra_example.drawio.png | Bin 0 -> 69437 bytes 4 files changed, 161 insertions(+), 162 deletions(-) rename docs/{ => illustrations}/brick_elements.drawio.png (100%) create mode 100644 docs/illustrations/brick_elements_dependency.drawio.png create mode 100644 docs/illustrations/infra_example.drawio.png diff --git a/docs/brick_concept_and_dependencies.md b/docs/brick_concept_and_dependencies.md index ac4f11c..ab48c77 100644 --- a/docs/brick_concept_and_dependencies.md +++ b/docs/brick_concept_and_dependencies.md @@ -1,224 +1,223 @@ -# Brick concept and dependencies +# Brick Concept and Dependencies -This docs will help you to understand how to represent a brick and its -sub-element, understand how playing with these element will let us manage the 3 dependency types. +This documentation will help you understand how to represent a brick and its +sub-elements and how manipulating these elements will enable us to manage the +three types of dependencies. -To understand the why of this concept you should read this article: [philosophy]( -./philosophy.md) +To grasp the rationale behind this concept, you should read this article: +[philosophy](./philosophy.md). -## Define brick concept +## Defining the Brick Concept -### In short words +### In Brief -First, exeIaC deals with infra bricks. What is an infra brick ? Basically it's -a directory that contains some infra code. It can be a terraform directory to -deploy a VM, an ansible playbook to configure an host, an helm chart or simply -a template that describe some instruction to do manually. +Firstly, exeIaC deals with infra bricks. What is an infra brick? Essentially, +it's a directory containing infrastructure code. This could be a terraform +directory for deploying a VM, an ansible playbook for configuring a host, a +helm chart, or simply a template describing manual instructions. -### Try to go deeper +### Delving Deeper -To manage our infra, exeIaC only manage brick (it's far more simple than have -too much concept). So an infra have to be seen as one super brick that contains -bricks, that can contains bricks. +In order to manage our infrastructure in a simple way, exeIaC solely focuses on +one concept : the brick. Therefore, an infra can be seen as one super brick +containing bricks, which in turn can contain more bricks and so on. -Bricks are layed on other bricks and so depends of brick that have -been layed previously. Where are thoose dependencies ? We have only one concept -so those dependency are in bricks too. +Bricks are layed on other bricks and thus depend on previously laid bricks. +Where are these dependencies? Since we only have one concept, these dependencies +are also within bricks. -In a devops way of thinking we want to have the most comprehensive -approach of infra. So the infra should contain all elements and process to -deploy and maintain an application on line. So a brick is not only the -terraform code it is also the makefile or the documentation to deploy it (and -not only deploy it but also plan, lint...) +From a DevOps perspective, we aim for a comprehensive approach to infra. Thus, +infrastructure should contain all elements and process to deploy and maintain an +application on line. Therefore, a brick encompasses not only the Terraform code +but also includes the Makefile for deployment (and not just deployment but also +planning, linting, etc.). -If we write the last 2 ingredients in our brick we should be able to have the -intelligence to deploy our infra in the right order. But it still miss -something. An infra lives and sometime you update someting. And as in a wall, -you can't change a brick in a middle of the wall without update brick on that -changed brick, in infra it's the same. And we may have something like events -that triggers a re-deploy. +If we write the last two ingredients in our brick, the infrastructure will know +the correct deploying order. However, something is still missing. Infra evolves, +and sometimes updates are necessary. Similar to a wall, you can't change a brick +in the middle of the wall without updating surrounding bricks. Similarly, in +infra, changes require updates to dependent bricks, and we may have events that +trigger redeployment. -So a brick contains : -- Code description: the infra code (terraform, ansible...) -- Commands: the brick interface to let us know how to deploy, plan, get the - specific state of a brick... -- Output: the infra specific state as the password generated or the ip address - assigned during the deploy of the brick -- State: the infra real state : does the infra drifted or have already been - deployed -- Input: Some data of its environment (other brick output) taken in input of - some command -- Trigger: some trigger to know when it should be re-deploy -- Event: that will trigger other bricks command. +Thus, a brick contains: +- Code description: The infra code (terraform, ansible...) +- Commands: The brick interface for deploying, planning, obtaining specific + brick state, etc. +- Output: Infra-specific states such as generated passwords or assigned IP + addresses during brick deployment. +- State: The real infra state - whether the infra has drifted or has already + been deployed. +- Input: Environment data (output from other bricks) used as input for some + commands. +- Event: command data output that could trigger other bricks commands. +- Trigger: Event observer that will redploy if necessary. -So brick's input and output are static. They describe a state of the infra. -Event and trigger are ephemeral they describe what happens and what should be -done. +Brick inputs and outputs are static, describing infra states. Events and +triggers are ephemeral, describing occurrences and actions. -![](./brick_elements.drawio.png) +![Brick Elements](./illustrations/brick_elements.drawio.png) -So command can be execute by human after modifying code or can be triggered by -an event of an other brick. +Commands can be executed by humans after modifying code or triggered by an other +bricks' event. -Command takes in input some output of other bricks and the code description. +Commands' input are other bricks' outputs and the code description. -Command also generate as output some events. Those events could be : -- the deploy correct a drift or have nothing to do -- the deploy have changed this output value -- the deploy have recreated this VM instance (because the specs of the VM - you can find in output haven't change so it's not visible by watching outputs) +Commands also generate events as output. These events could indicate: +- Successful deployment correcting a drift or having nothing to do +- Changes in output values during deployment +- Recreation of a VM instance due to unchanged specs (not reflected in brick's + outputs) +## How exeIaC Implements It -## How exeIaC implement it +For exeIaC, the brick command is a module that can be reuse by other bricks. So +it permits factorization. Essentially, a module is an executable with a +standardized interface that can be enriched. The module interface should contain +a method to specify the implemented interface and how to retrieve events for +each method. [Learn more about writing modules](./howto_write_module.md). -For exeIaC the brick command is a module. It permit factorization. And basically it's -an executable with a standardized interface that can be enriched. -The module interface should contain a method to specify what is the implemented interface and how to retrieve events for each method. -[How to write module](./howto_write_module.md) +The input needed for each command method and how to present it is defined in a +`brick.yml` file inside the brick directory. +[Learn how to write a brick](./howto_write_brick.md). -What input is needed for what command method and how to present it is defined -in a brick.yml file inside the brick directory. -[How to write a brick](./howto_write_brick.md) - - -## Dependency types +## Dependency Types ### Examples -To explore all the three dependency types, we will base us on an example. In this example we will focus on the link between 3 brick of an infra. -Here the brick list and their outputs : +To explore all three dependency types, we'll base our discussion on an example +involving the interconnection of three bricks within an infra. Here's the list +of bricks and their outputs: - A: a cloud VM instance - - output: - - hw_spec.ram - - hw_spec.cpu - - event: - - vm_recreated + - Output: + - `hw_spec.ram` + - `hw_spec.cpu` + - Event: + - `vm_recreated` - B: configuration of that VM instance - - input: - - hw_spec.ram -- C: a hop ssh server that is needed to connect and configure the instance - - output: - - ip_address - - credentials + - Input: + - `hw_spec.ram` +- C: a hop SSH server needed to connect and configure the instance + - Output: + - `ip_address` + - `credentials` -The list of outputs are not exhaustive in a real case but we only need -those to explain the three type of dependency. +The outputs list is not exhaustive but sufficient to explain the three types of +dependencies. -`schema` +![Dependency Schema](./illustrations/infra_example.drawio.png) - -### Precise what is a dependency +### Precising Dependency Definitions -Ok brick depends of each other and of course we can't create a VM on a network -that doesn't exist or configure a VM that doesn't exist. But in what way define -different type of dependencies can be usefull ? +Ok brick depends of each other. Actually it's logical because we can't configure +a VM that doesn't exist. But why defining different types of dependencies can be +usefull? -We search to define different type of dependency to know when we have to -re-deploy a brick. It's not because a brick has changed that you have to -re-deploy all bricks on top of it. If I install vim on my hop ssh server, I -don't need to redeploy because it's not a dependency. +We seek to define different types of dependencies in order to know when we have +to re-deploy a brick. Actually a brick changes doesn't mean all bricks above it +need redeployment. For example, installing vim on an SSH server does not +necessitate redeploying configurations. -So a dependency is a data from brick's output or event. A dependency can be an -input or a trigger or perhaps both. +A dependency refers to data from a brick's output or event. It can be an input, +a trigger, or both (a trigger which is not only a boolean). -But we call that dependency, what brick element depends of a dependency in an -implementation point of view ? It's the command element (module) that need -dependencies to execute itself. +But in terms of implementation, what brick's sub-element use dependencies datas? +It's the command element (module) that needs dependencies to execute itself. -All command method needs input ? Maybe but not the same. If you implement a -lint method to check your code I doubt that it needs any input. Same, maybe -for a terraform apply you need to have a tfvars file, but to make a terraform -out you only need acces to the state. +While all command methods may need input, they don't necessary needs the same +inputs. For instance : +- a lint method for code checking likely requires no input +- a `terraform apply` may need a `tfvars` file +- a `terraform output` only needs credentials to access the state. -So a dependency is a data that you take from an other brick output or event and -that you need for some specific module method. +Dependency is data retrieved from another brick's output or event, necessary for +specific module methods. Each dependency can potentially trigger a specific +brick's module method. -And each dependency can trigger or not a specific brick's module method. +![Dependency Schema](./illustrations/brick_elements_dependency.drawio.png) -### The classic dependency +### The Classic Dependency -This is the most common : when the input changes, it triggers a redeploy. -For example for brick B it could be: A.hw_specs.ram to reconfigure the java -heapsize. +This is the most common type : when the input changes, it triggers a redeploy. +For example, for brick B, the dependency could be `A.hw_specs.ram` triggering a +re-deployment in order to reconfigurate the java heap size. -### The weak dependency +### The Weak Dependency -The module need the data as input to excute, but the change of the data won't -trigger a re-deploy. For example to deploy the brick B you need to get -C.ip_address and C.credentials to connect to VM but actually, if C.credentials -change it won't impact the brick B configuration. +The module needs data as input to execute, but changes in that data won't +trigger a redeploy. For instance, to deploy brick B, you need to access +`C.ip_address` and `C.credentials` to connect to the VM. However, if +`C.credentials` change, it won't impact brick B's configuration. -### The special dependency +### The Special Dependency -This category contains all dependency that trigger a re-deploy but are not an -input change. -For example, if you want to destroy and recreate with the same input the VM of -brick A or even you destroy and recreate it without changing any output readed -as input of brick B. You need to re-deploy the configuration of the VM (brick -B). Actually the configuration has disapear. -You can do that by testing the boolean A.vm_recreated. +This category includes dependencies triggering a redeploy but not due to input +changes. For instance, destroying and recreating the VM of brick A without +altering any output read as input for brick B necessitates redeploying brick +B's configuration. -Else, brick A could have as output a vm-id or a creation-timestamp that could be -manage as a classic dependency. But you see here that this dependency value -isn't needed as input. +It exists 3 way to implement this kind of dependency: +- testing the boolean event `A.recreated_vm` +- testing if event list `A.recreated_vms_list` contains your vm name. Here the + event has a value. +- testing if output `A.vm-id` or `A.creation-timestamp` has changed. Here you + manage special dependency as a classic dependency with the difference that + the data is not needed as input. -We can also imagine that brick A create lot of VM so it's event could be a list -of recreated VMs. So the trigger would be a test on a data type list. +For exeIaC, a special dependency is merely a test unrelated to value changes. -As you see we can imagine many special dependency. For exeIaC it's only a test -that is not linked to a change of a value. +### Dependencies that don't trigger a simple redeploy -### What other thing the system permit +The system permits actions other than simple deployment (lay). This flexibility +is useful for implementing various deployment functions, such as 1-10-100 +deployments or a special redeployment for password renewals. -We can trigger other action than a simple deploy (lay). It is usefull if you -want to implement many different lay function. For example for 1-10-100 deploy -or for renewing password. +## From Elementary Brick to Infra +### Elementary Brick -## From elementary brick to infra +An elementary brick is a brick that contain infra code. It is the smallest infra +element. What we've defined as +[Sub-elements of a Brick](#sub-element-of-a-brick) describe actually an +elementary brick. -### Elementary brick +### Higher-Order Brick -An elementary brick is a brick that contain infra code. It's the smallest -element of an infra. What we have define in [Sub-element of a brick](#sub-element-of-a-brick) is actually an elementary brick. +A higher-order brick only contains elementary bricks. Executing a higher-order +brick involves executing all sub-bricks in the correct order. The input of a +higher-order brick comprises the inputs of all sub-bricks (excluding those +referencing other sub-bricks' outputs). The output of a higher-order brick +includes all outputs of its contained elementary bricks. -### Higher order brick +An higher-order brick is not seen very differently from an elementary brick. +Sometimes, exeIaC's vision is solely based on elementary bricks, but let's +redefine brick elements for a higher-order brick: +- **Input & Triggers**: All inputs and triggers of its sub-bricks, minus inputs + and events referencing other sub-bricks. +- **Output & Events**: All outputs and events of its sub-bricks. +- **Command**: Deploying a higher-order brick involves deploying all its + sub-bricks in the correct order. -An higher order brick only contains some elementary bricks. -To execute an higher-order brick you have to execute in the right order all -sub-bricks. The input of an higher order brick correspond to the inputs of -all the sub-brick (without input that call output of the higher-order brick). -The output of an higher order brick correspond to all the output of elementary - bricks it contains. -An higher order brick is not seen very differently as an elementary brick. -Sometimes exeIaC vision is only based on elementary bricks but lets re-define -brick's element for an higher order brick -- **input & triggers**: all inputs and triggers of it's sub-brick minus inputs - and events that reference other sub-bricks -- **output & events**: all outputs and events of its sub-bricks -- **command**: deploy an higher order brick is deploy all it's sub-brick in the - right order. +**NB: Execution Order** to rework -**NB: execution order** to rework -As you can see in page [how to write a brick](./howto_write_brick.md) each -brick has its directory name prefixed by a priority number. So take care -of your higher-order brick composition. Actually in a higher-order brick you -will execute all brick with the priority you see. A higher order brick will -be seen as an elementary brick. So it is simple for a human to read his/her -infra code. You won't have to understand how to execute a brick to execute it. +As you can see on the page [how to write a brick](./howto_write_brick.md), each +brick has its directory name prefixed by a priority number. So, take care in +composing your higher-order bricks. In a higher-order brick, you will execute +all bricks according to the priority you see. A higher-order brick will be +perceived as an elementary brick. Therefore, it's straightforward for a human to +read their infra code. You won't need to understand how to execute a brick to +execute it. ### Room -It's an higher order brick that correspond to a directory and that have to be +A room is a higher-order brick that corresponds to a directory and must be listed in the [exeIaC configuration file](./howto_write_configuration_file.md) -to be seen by exeIaC. It's convention are a bit different than other brick, but -it can be considered as a brick. +to be recognized by exeIaC. Its conventions are slightly different from other +bricks, but it can be considered as a brick. -Usually it is a git repository. +Usually, it is a git repository. ### Infra -An infra is an ordered list of rooms. So it's also a brick. A self sufficient -brick with no input, and triggers and with no output and events. - +An infra is an ordered list of rooms. So, it's also a brick. It's a +self-sufficient brick with no input or triggers and with no output or events. diff --git a/docs/brick_elements.drawio.png b/docs/illustrations/brick_elements.drawio.png similarity index 100% rename from docs/brick_elements.drawio.png rename to docs/illustrations/brick_elements.drawio.png diff --git a/docs/illustrations/brick_elements_dependency.drawio.png b/docs/illustrations/brick_elements_dependency.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..273214c6b8b06cde0951e873b18fae95186e6b56 GIT binary patch literal 35485 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 U;JZCYg8>LUUHx3vIVCg!0RHl1QUCw| literal 0 HcmV?d00001 From da0f7a7cfe11bde84d652ca18577a9ed987eb44f Mon Sep 17 00:00:00 2001 From: arthur91f Date: Thu, 4 Apr 2024 17:46:17 +0200 Subject: [PATCH 7/8] docs: correct english --- README.md | 91 +++++++-------- docs/howto_write_brick.md | 88 +++++++------- docs/howto_write_configuration_file.md | 153 ++++++++++--------------- docs/howto_write_module.md | 131 ++++++++------------- 4 files changed, 196 insertions(+), 267 deletions(-) diff --git a/README.md b/README.md index b4ec43e..4d6ddb9 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,94 @@ # Exeiac ## Why use exeIaC -`exeiac` is a tool that enables infrastructure folks to handle several -different provisionning IaC (Infrastructure as Code) tools, under a -single CLI, and helps solving some recurrent paintpoints with IaC. +`exeiac` is a tool that enables infrastructure folks to handle several +different provisioning IaC (Infrastructure as Code) tools under a single CLI +and helps solve some recurrent pain points with IaC. -It follows the brick convention, which describes an infrastructure as -a set of bricks. A brick is a piece of infrastructure, it -simultaneously is the actual infrastructure element, the code that -describes it and a piece code that allows its execution; be it a -terraform state, an ansible playbook or a helm chart for instance. +It follows the brick convention, which describes an infrastructure as a set of +bricks. A brick is a piece of infrastructure; it simultaneously is the actual +infrastructure element, the code that describes it, and a piece of code that +allows its execution; be it a terraform state, an ansible playbook, or a helm +chart, for instance. This project was born from the following needs: - solve dependencies issues -- increase transparency as to how a piece of infrastructure should be - deployed. No matter the provisionning tool you use, if there's a - module for it, `exeiac` will handle it -- allow for a clean way to interact with only a part of your - infrastructure in a safe way, without breaking the dependency tree, - even if your infrastructure management tool doesn't provide that - feature +- increase transparency as to how a piece of infrastructure should be deployed. + No matter the provisioning tool you use, if there's a module for it, `exeiac` + will handle it +- allow for a clean way to interact with only a part of your infrastructure in + a safe way, without breaking the dependency tree, even if your infrastructure + management tool doesn't provide that feature ## What is a brick -First, exeIaC deals with infra bricks. What is an infra brick ? Basically -it's a directory that contains some infra code. It can be a terraform directory -to deploy a VM, an ansible playbook to configure an host, an helm chart or simply -a template that describe some instruction to do manually. +First, exeIaC deals with infra bricks. What is an infra brick? Basically, it's +a directory that contains some infra code. It can be a terraform directory to +deploy a VM, an ansible playbook to configure a host, a helm chart, or simply a +template that describes some instructions to do manually. -To understand this page it's sufficient. But if you want to understand deeper -the genious idea behind that concept, how many type of dependencies exist -between your bricks and all element that compose your brick (code description, -input, ouput, events, trigger, command), I let you read that [page](./docs/brick_concept_and_dependencies) +This summary should be enough for you to be able to read the following +paragraphs and have a basic understanding of exeIaC. But if you want to get a +better grasp of the brick concept itself and its genius simplicity, you can +find a deeper insight on +[this documentation page](./docs/brick_concept_and_dependencies). -## Get started and how it works summary +## Get started - **1. Get exeiac binary** - ``` bash + ```bash go install github.com/arthur91f/exeiac/src/exeiac/src/exeiac@main ``` - **2. Write your modules** in whatever language you want. A module can be seen - as a makfile to deploy your brick. Basically it's a shell script that follow some - conventions describe here : [How to write module](./docs/howto_write_module.md) - You have to implement three command: - - *describe_module_for_exeiac* (that display a json) + as a makefile to deploy your brick. Basically, it's a shell script that + follows some conventions described here: + [How to write module](./docs/howto_write_module.md). You have to implement + three commands: + - *describe_module_for_exeiac* (that displays a json) - *lay* to deploy your brick - - *output* to display some specs of your brick as ip address, login... - - ... you can implement other command as plan, remove, lint, help... - -- **3. Put a yaml file in each IaC directory** to describe your bricks as here + - *output* to display some specs of your brick such as IP address, login... + - ... you can implement other commands like plan, remove, lint, help... + +- **3. Put a YAML file in each IaC directory** to describe your bricks as here [How to write brick](./docs/howto_write_brick.md). It will let: - exeiac identify your IaC directory as an infra brick - associate your brick to its module - - exeiac understand your brick's dependencies and how to present it to the brick + - exeiac understand your brick's dependencies and how to present them to the + brick -- **4. Write your exeiac conf file** in your home or in /etc to let exeiac binary - find your module and your infra code. +- **4. Write your exeiac conf file** in your home or in /etc to let exeiac + binary find your module and your infra code. [How to write config file](./docs/howto_write_configuration_file.md) - **5. Enjoy exeiac** - Here some example of basic command you can execute + Here are some examples of basic commands you can execute: - Display output of a brick ```bash exeiac output infra-ground/envs/staging/network ``` - - Plan all sub-brick of infra-ground/envs/staging + - Plan all sub-bricks of infra-ground/envs/staging ```bash exeiac output infra-ground/envs/staging ``` - - Display all bricks should be re-deploy after the change of the brick's network - output .network.ip_range + - Display all bricks that should be re-deployed after the change of the + brick's network output .network.ip_range ```bash exeiac get-depends infra-ground/envs/staging/network -j $.network.ip_range ``` - - Display all bricks that can be impacted by the re-deploy of the birck network + - Display all bricks that can be impacted by the re-deploy of the brick network ```bash exeiac show infra-ground/envs/staging/network --bricks-specifiers linked_next --format name ``` - - Deploy/re-deploy a drift in brick staging/network and all other bicks + - Deploy/re-deploy a drift in brick staging/network and all other bricks impacted by that drift recursively. ```bash exeiac smart-lay infra-ground/envs/staging/network --non-interactive - + ``` ## Useful links - **local** - [How to join us](./docs/to_write.md) -- **externe** +- **external** - [Download exeiac](https://download-exeiac.91f.ovh) - [exeIaC presentation](https://drive.google.com/blabla) - diff --git a/docs/howto_write_brick.md b/docs/howto_write_brick.md index 6c62c49..d6261e5 100644 --- a/docs/howto_write_brick.md +++ b/docs/howto_write_brick.md @@ -1,72 +1,71 @@ -# Write your brick +```markdown +# Write Your Brick ## Generality -A brick is an element of your infra and it's IaC representation. It can be a -terraform state, an ansible playbook, an helm charts, a combination all thoose -things or even other things depending of your brick's module. +A brick is an element of your infra and its IaC representation. It can be a +terraform state, an ansible playbook, a helm chart, a combination of all those +things, or even other elements depending on your brick's module. A brick is represented by a directory. -But a brick is not only the code describing the real infra element. -The elements describing a brick are : +But a brick is not only the code describing the real infra element. +The elements describing a brick are: - code: content of the directory -- outputs: the state of the brick, the value taken by things declared in codes - as genereated password, given public ipaddress or just some value that can be - used by other bricks -- inputs: describe the context of the brick, it is composed by parts of outputs +- outputs: the state of the brick, the values taken by things declared in codes + as generated passwords, given public IP addresses, or just some values that + can be used by other bricks +- inputs: describe the context of the brick, it is composed of parts of outputs of other bricks -- module events: output of the module action they are describing what have been +- module events: output of the module action, they are describing what has been done not the state of the brick (that is described by outputs) - triggers: input change or other brick module's event that will trigger a lay or another action on the brick -**Note: elementary bricks vs higher order bricks:** Every brick is represented -by a directory. Some brick contains other bricks and some other just contain a -brick.yml file that describe its module, inputs, triggers ... +**Note: Elementary Bricks vs Higher-Order Bricks:** Every brick is represented +by a directory. Some bricks contain other bricks and some others just contain a +brick.yml file that describes its module, inputs, triggers... -## Directory convention +## Directory Convention -You can identify brick from their path: +You can identify bricks from their path: - A brick must be in a "room" (listed in [exeiac configuration file](./howto_write_configuration_file.md)) or be a room. -- A brick directory name must begin with a priority number that describe the +- A brick directory name must begin with a priority number that describes the building sequence of bricks inside the same room or super brick. -**Note: why the priority number ?** Actually it's redundant with dependencies -information and exeIaC should be able to build its valid laying sequence. But -we wanted to have a convention that let humans easily partially guess some layer -and dependency tree. It is also useful to identify easily what directory is a -brick. Based on this you can add documentation directory or a directory with -maintenance script as dump and restore database that will be ignored by exeiac. +**Note: Why the Priority Number?** Actually, it's redundant with dependency +information, and exeIaC should be able to build its valid laying sequence. But +we wanted to have a convention that lets humans easily partially guess some +layer and dependency tree. It is also useful to identify easily what directory +is a brick. Based on this, you can add a documentation directory or a directory +with maintenance scripts like dump and restore database that will be ignored by exeiac. +## Writing brick.yml File -## Writing brick.yml file - -In each elementary brick directory you have to write a brick.yml file. -It needs to contains thoose fields: -- **version**: the version of the file. At the moment we are in 1.0.0 -- **module**: the module name or a relative path to the module beginning by - ./ -- **dependencies**: a dictionnary of input values needed for execute the brick. +In each elementary brick directory, you have to write a brick.yml file. +It needs to contain these fields: +- **version**: the version of the file. At the moment, we are in 1.0.0 +- **module**: the module name or a relative path to the module beginning by ./ +- **dependencies**: a dictionary of input values needed to execute the brick. - **from**: the format is brickname:source:jsonpath - _brickname_: the brick name of the dependencies - _source_: can be *"output"* or *"event"* - - _jsonpath_: the json path of the value for example: *$.network.ip_range* - - **needed_for**: list of actions that needs that dependency. - The default is setted in exeiac configuration files - - **not_needed_for**: list of actions that don't needs that dependency. - The default is setted in exeiac configuration files + - _jsonpath_: the JSON path of the value, for example: *$.network.ip_range* + - **needed_for**: list of actions that need that dependency. The default is + set in exeiac configuration files + - **not_needed_for**: list of actions that don't need that dependency. The + default is set in exeiac configuration files - **triggered_actions**: what action will be triggered by that dependency. Default is lay - **trigger_type**: default is classic. Can be: - - _classic_: if the value change it will trigger the action in *trigger_action* - To use if the input is also a spec of your brick - - _weak_: if the value change it won't trigger anything. - To use if the input is needed for execution but is not a spec of your brick. - For example the hop ssh server credentials used to connect to your VM. - - It can also be a special trigger type usually used with events. It will be a - function as: + - _classic_: if the value changes, it will trigger the action in + *trigger_action*. To use if the input is also a spec of your brick + - _weak_: if the value changes, it won't trigger anything. To use if the + input is needed for execution but is not a spec of your brick. For example, + the hop SSH server credentials used to connect to your VM. + - It can also be a special trigger type usually used with events. It will + be a function as: - is_true() - is_false() - is_not_null() @@ -77,9 +76,8 @@ It needs to contains thoose fields: - not_contains(string) - match(string) - not_match(string) - - **inputs**: how to present dependencies value to the module. see below - **type**: can be *env_vars* or *file* - **format**: can be *env* or *json* - - **path**: the relative path if it's type file else let to default "" + - **path**: the relative path if it's type file else left to default "" - **datas**: the list of the dependencies keys diff --git a/docs/howto_write_configuration_file.md b/docs/howto_write_configuration_file.md index b4ec43e..272a3e1 100644 --- a/docs/howto_write_configuration_file.md +++ b/docs/howto_write_configuration_file.md @@ -1,93 +1,60 @@ -# Exeiac - -## Why use exeIaC -`exeiac` is a tool that enables infrastructure folks to handle several -different provisionning IaC (Infrastructure as Code) tools, under a -single CLI, and helps solving some recurrent paintpoints with IaC. - -It follows the brick convention, which describes an infrastructure as -a set of bricks. A brick is a piece of infrastructure, it -simultaneously is the actual infrastructure element, the code that -describes it and a piece code that allows its execution; be it a -terraform state, an ansible playbook or a helm chart for instance. - -This project was born from the following needs: -- solve dependencies issues -- increase transparency as to how a piece of infrastructure should be - deployed. No matter the provisionning tool you use, if there's a - module for it, `exeiac` will handle it -- allow for a clean way to interact with only a part of your - infrastructure in a safe way, without breaking the dependency tree, - even if your infrastructure management tool doesn't provide that - feature - -## What is a brick -First, exeIaC deals with infra bricks. What is an infra brick ? Basically -it's a directory that contains some infra code. It can be a terraform directory -to deploy a VM, an ansible playbook to configure an host, an helm chart or simply -a template that describe some instruction to do manually. - -To understand this page it's sufficient. But if you want to understand deeper -the genious idea behind that concept, how many type of dependencies exist -between your bricks and all element that compose your brick (code description, -input, ouput, events, trigger, command), I let you read that [page](./docs/brick_concept_and_dependencies) - -## Get started and how it works summary - -- **1. Get exeiac binary** - ``` bash - go install github.com/arthur91f/exeiac/src/exeiac/src/exeiac@main - ``` - -- **2. Write your modules** in whatever language you want. A module can be seen - as a makfile to deploy your brick. Basically it's a shell script that follow some - conventions describe here : [How to write module](./docs/howto_write_module.md) - You have to implement three command: - - *describe_module_for_exeiac* (that display a json) - - *lay* to deploy your brick - - *output* to display some specs of your brick as ip address, login... - - ... you can implement other command as plan, remove, lint, help... - -- **3. Put a yaml file in each IaC directory** to describe your bricks as here - [How to write brick](./docs/howto_write_brick.md). It will let: - - exeiac identify your IaC directory as an infra brick - - associate your brick to its module - - exeiac understand your brick's dependencies and how to present it to the brick - -- **4. Write your exeiac conf file** in your home or in /etc to let exeiac binary - find your module and your infra code. - [How to write config file](./docs/howto_write_configuration_file.md) - -- **5. Enjoy exeiac** - Here some example of basic command you can execute - - Display output of a brick - ```bash - exeiac output infra-ground/envs/staging/network - ``` - - Plan all sub-brick of infra-ground/envs/staging - ```bash - exeiac output infra-ground/envs/staging - ``` - - Display all bricks should be re-deploy after the change of the brick's network - output .network.ip_range - ```bash - exeiac get-depends infra-ground/envs/staging/network -j $.network.ip_range - ``` - - Display all bricks that can be impacted by the re-deploy of the birck network - ```bash - exeiac show infra-ground/envs/staging/network --bricks-specifiers linked_next --format name - ``` - - Deploy/re-deploy a drift in brick staging/network and all other bicks - impacted by that drift recursively. - ```bash - exeiac smart-lay infra-ground/envs/staging/network --non-interactive - - -## Useful links - -- **local** - - [How to join us](./docs/to_write.md) -- **externe** - - [Download exeiac](https://download-exeiac.91f.ovh) - - [exeIaC presentation](https://drive.google.com/blabla) - +```markdown +# Write Your Brick + +## Generality + +A brick is an element of your infra and its IaC representation. It can be a Terraform state, an Ansible playbook, a Helm chart, a combination of all those things, or even other elements depending on your brick's module. + +A brick is represented by a directory. + +But a brick is not only the code describing the real infra element. The elements describing a brick are: +- code: content of the directory +- outputs: the state of the brick, the values taken by things declared in codes as generated passwords, given public IP addresses, or just some values that can be used by other bricks +- inputs: describe the context of the brick, it is composed of parts of outputs of other bricks +- module events: output of the module action, they are describing what has been done not the state of the brick (that is described by outputs) +- triggers: input change or other brick module's event that will trigger a lay or another action on the brick + +**Note: Elementary Bricks vs Higher-Order Bricks:** Every brick is represented by a directory. Some bricks contain other bricks and some others just contain a brick.yml file that describes its module, inputs, triggers... + +## Directory Convention + +You can identify bricks from their path: +- A brick must be in a "room" (listed in [exeiac configuration file](./howto_write_configuration_file.md)) or be a room. +- A brick directory name must begin with a priority number that describes the building sequence of bricks inside the same room or super brick. + +**Note: Why the Priority Number?** Actually, it's redundant with dependency information, and exeIaC should be able to build its valid laying sequence. But we wanted to have a convention that lets humans easily partially guess some layer and dependency tree. It is also useful to identify easily what directory is a brick. Based on this, you can add a documentation directory or a directory with maintenance scripts like dump and restore database that will be ignored by exeiac. + +## Writing brick.yml File + +In each elementary brick directory, you have to write a brick.yml file. It needs to contain these fields: +- **version**: the version of the file. At the moment, we are in 1.0.0 +- **module**: the module name or a relative path to the module beginning by ./ +- **dependencies**: a dictionary of input values needed to execute the brick. + - **from**: the format is brickname:source:jsonpath + - _brickname_: the brick name of the dependencies + - _source_: can be "output" or "event" + - _jsonpath_: the JSON path of the value, for example: *$.network.ip_range* + - **needed_for**: list of actions that need that dependency. The default is set in exeiac configuration files + - **not_needed_for**: list of actions that don't need that dependency. The default is set in exeiac configuration files + - **triggered_actions**: what action will be triggered by that dependency. Default is lay + - **trigger_type**: default is classic. Can be: + - _classic_: if the value changes, it will trigger the action in *trigger_action*. To use if the input is also a spec of your brick + - _weak_: if the value changes, it won't trigger anything. To use if the input is needed for execution but is not a spec of your brick. For example, the hop SSH server credentials used to connect to your VM. + - It can also be a special trigger type usually used with events. It will be a function as: + - is_true() + - is_false() + - is_not_null() + - is_null() + - is_equal(string) + - is_different(string) + - contains(string) + - not_contains(string) + - match(string) + - not_match(string) +- **inputs**: how to present dependencies value to the module. see below + - **type**: can be env_vars or file + - **format**: can be env or json + - **path**: the relative path if it's type file else left to default "" + - **datas**: the list of the dependencies keys +``` +Ce texte peut être copié et collé dans un fichier Markdown pour être utilisé. \ No newline at end of file diff --git a/docs/howto_write_module.md b/docs/howto_write_module.md index 7946b9c..fb7a2eb 100644 --- a/docs/howto_write_module.md +++ b/docs/howto_write_module.md @@ -1,36 +1,21 @@ -# Writting your module +# Writing Your Module ## Generality -A module is an executable that will be called by exeIaC to run an action. -The module will implement an interface. Some of the method of this interface -have to be implemented some other can be omitted. You can also overload this -interface with some other actions. -These actions will be describe more precisely later but here some : -- **describe_module_for_exeiac**: mandatory, used by exeIaC to get action - implemented by the module. -- **lay**: mandatory, to deploy or correct drift on a brick -- **remove**: mandatory, rollback a lay. Is less mandatory than the 2 previous - action, because you can manage your infra by removing by hand. -- **output**: display output information of a brick - (mandatory if you want some brick depends of brick using this module) -- **plan**: check if a lay is needed and/or what the lay will do -- **help**: display a specific help for the module or more specific for the - brick -- **init**: install or check all prerequisite for the module -- **clean**: remove all files created by the module -- **validate_code**: validate teh syntax of the brick's code +A module is an executable that will be called by exeIaC to run an action. The module will implement an interface. Some of the methods of this interface have to be implemented, while others can be omitted. You can also overload this interface with additional actions. These actions will be described more precisely later, but here are some examples: +- **describe_module_for_exeiac**: mandatory, used by exeIaC to get actions implemented by the module. +- **lay**: mandatory, to deploy or correct drift on a brick. +- **remove**: mandatory, rollback a lay. This action is less mandatory than the previous two because you can manage your infra by removing manually. +- **output**: display output information of a brick (mandatory if you want some brick depends on a brick using this module). +- **plan**: check if a lay is needed and/or what the lay will do. +- **help**: display specific help for the module or more specific for the brick. +- **init**: install or check all prerequisites for the module. +- **clean**: remove all files created by the module. +- **validate_code**: validate the syntax of the brick's code. - ... it can be overloaded -**Note: module actions aren't exeiac commands !** -You may recognize some exeIaC actions but they are not exactly the -same. For example the module lay can be just a _terraform apply_. The exeIaC -_exeiac lay_ will of course execute the module lay but before it will search -all the bricks it needs to output to get the input of the lay. Then it will -output the current brick and register this output and after the module lay -it will output again the current brick to compare the output before and after -and say if it has changed. But happily it's exeIaC that do all of that, you -just have to code the module lay and output and specify the inputs needed. +**Note: module actions aren't exeiac commands!** +You may recognize some exeIaC actions, but they are not exactly the same. For example, the module lay can be just a _terraform apply_. The exeIaC _exeiac lay_ will of course execute the module lay, but before it will search all the bricks it needs to output to get the input of the lay. Then it will output the current brick and register this output and after the module lay, it will output again the current brick to compare the output before and after and determine if it has changed. But happily it's exeIaC that does all of that; you just have to code the module lay and output and specify the inputs needed. So you haven't to implement: - show @@ -39,96 +24,74 @@ So you haven't to implement: ## Think to specify your module in your exeIaC and bricks conf -Find a name to your module. It will be used in : +Find a name for your module. It will be used in: - brick.yml files: at field .module - exeIaC conf: at field modules[].name and specify the full path of your module +## Module's Inputs Conventions -## Module's inputs conventions - -- **Brick path**: before call module exeIaC will change directory to brick path -- **Action**: module takes one positionnal argument that is the *action* name -- **Options**: module can takes some other options as following - - --non-interactive (is passed to module by exeiac interpretating -i, - -interactive, -I or --non-interactive options) - - other option of your invention can be passed with -o or --other-options -- **exeIaC env variables**: exeIaC define some env variables that can be used - by modules +- **Brick Path**: before calling module exeIaC will change directory to the brick path. +- **Action**: module takes one positional argument that is the *action* name. +- **Options**: module can take some other options as follows: + - --non-interactive (is passed to module by exeiac interpreting -i, -interactive, -I or --non-interactive options). + - other options of your invention can be passed with -o or --other-options. +- **exeIaC Env Variables**: exeIaC defines some env variables that can be used by modules: - EXEIAC_BRICK_PATH - EXEIAC_BRICK_NAME - EXEIAC_ROOM_PATH - EXEIAC_ROOM_NAME - EXEIAC_MODULE_PATH - EXEIAC_MODULE_NAME -- **Brick inputs**: brick can define some inputs. It can be a file or env vars - (look at howto_write_brick_yaml.md) - +- **Brick Inputs**: brick can define some inputs. It can be a file or env vars (look at howto_write_brick_yaml.md). -## Module's outputs conventions +## Module's Outputs Conventions -The different actions have different behaviour. For some the stdout will be -displayed, for other it will be registered inside. For some the status code -will be interpretated, for some other not. +The different actions have different behavior. For some, the stdout will be displayed; for others, it will be registered inside. For some, the status code will be interpreted; for some others not. ### Stdout -Except for *describe_module_for_exeiac* and *output* where the stdout will be -consumed by exeIaC (and so need to follow some convention), all other actions -will just display the stdout without modify it. +Except for *describe_module_for_exeiac* and *output*, where the stdout will be consumed by exeIaC (and so need to follow some convention), all other actions will just display the stdout without modifying it. -For output and describe_module_for_exeiac, the expected format is json. -For output no other convention is needed, for describe_module_for_exeiac, check -the proper part +For output and describe_module_for_exeiac, the expected format is json. For output, no other convention is needed. For describe_module_for_exeiac, check the proper part. ### Stderr -The stderr will always be displayed without beeing consumed +The stderr will always be displayed without being consumed. -### Status code +### Status Code -By default the satus code understanding will be that -- 0: when action succeed -- 1-255: when a problem occurs (it will leads exeIaC to stop runs) +By default, the status code understanding will be that: +- 0: when the action succeeds. +- 1-255: when a problem occurs (it will lead exeIaC to stop runs). -But you can define some events that won't be consider as a fail for an action -by setting diplayed with describe_module_for_exeiac. -For example 0 correspond to no drift and 2 correspond there is/was a drift. +But you can define some events that won't be considered as a fail for an action by setting displayed with describe_module_for_exeiac. For example, 0 corresponds to no drift and 2 corresponds to there is/was a drift. ### describe_module_for_exeiac stdout -It displays a dictionnary of implemented actions: -Each action is a dictionnary with thoose fields: -- behaviour: that can be ommitted nowaday because exeIaC only implement one - valid default behaviour by action. But in may change in future. -- status_code_fail: default is "1-255" but you can change it -- events: dictionnary that contain thoose fields +It displays a dictionary of implemented actions: Each action is a dictionary with those fields: +- behaviour: that can be omitted nowadays because exeIaC only implements one valid default behavior by action. But it may change in the future. +- status_code_fail: default is "1-255" but you can change it. +- events: dictionary that contains those fields: - type: - - *status_code*: boolean set to true if it correspond to field - *status_code* - - *file*: string represent the content of the file - - *json*: the content of the file can represent whatever you want as - long as it is in json format - - *yaml*: the content of the file can represent whatever you want as - long as it is in yaml format - - status_code: only for type=*status_code* that is a number sequence as - "2,5-10" - - path: only for type=*file*, *json*, *yaml*. It can be a full path or a relative - path from the brick path. It can also be a classic file or a named pipe. - - For plan exeiac can interpret some special event to know if there is a drift - or not: + - *status_code*: boolean set to true if it corresponds to the field status_code. + - *file*: string represent the content of the file. + - *json*: the content of the file can represent whatever you want as long as it is in json format. + - *yaml*: the content of the file can represent whatever you want as long as it is in yaml format. + - status_code: only for type=*status_code* that is a number sequence as "2,5-10". + - path: only for type=*file*, *json*, *yaml*. It can be a full path or a relative path from the brick path. It can also be a classic file or a named pipe. + + For plan exeiac can interpret some special event to know if there is a drift or not: - for type *status_code*: - exeiac_plan_no_drift - exeiac_plan_drift - exeiac_plan_unknown - - for type *file*, *json* or *yaml*: the event name should be exeiac_plan - and the content should be in equal to: + - for type *file*, *json* or *yaml*: the event name should be exeiac_plan and the content should be in equal to: - no_drift - drift - unknown - A success will be considered as an exeiac_plan_unkown except if a previous event - is catched. + A success will be considered as an exeiac_plan_unknown except if a previous event is caught. + Example 1: ```json From db983578008208cc0dd634d090cc5632329fda53 Mon Sep 17 00:00:00 2001 From: arthur91f Date: Mon, 13 May 2024 23:07:04 +0200 Subject: [PATCH 8/8] docs(README.md): compilation and completion --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d6ddb9..d3546f6 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,18 @@ find a deeper insight on ## Get started -- **1. Get exeiac binary** +- **1. Get exeiac binary and install completion** ```bash - go install github.com/arthur91f/exeiac/src/exeiac/src/exeiac@main + asdf plugin add golang https://github.com/asdf-community/asdf-golang.git + asdf install golang 1.19 + git clone https://github.com/arthur91f/exeiac.git + cd exeiac/ + asdf local golang 1.19 + go install src/exeiac ``` + - bash completion: `cp './src/completion/scripts/exeiac.sh' '/usr/share/bash-completion/completions/exeiac'` + - zsh completion: `cp './src/completion/scripts/exeiac.zsh' '/usr/share/zsh/site-functions/_exeiac'` + - fish completion: `cp './src/completion/scripts/exeiac.fish' '/usr/share/fish/vendor_completions.d/exeiac.fish'` - **2. Write your modules** in whatever language you want. A module can be seen as a makefile to deploy your brick. Basically, it's a shell script that