# Cue 0.7 <!-- .element: style="font-size: 2.5em" -->
## Mise à jour de Cue 0.7 <!-- .element: style="font-size: 1em" -->
## sur env-ng et trackbone <!-- .element: style="font-size: 1em" -->
---
## Sommaire
1. Évolutions constatées
2. Problème de dépendances des taches flow
3. Problème de concurrence avec cue flow
4. Rappel sur la fermeture (close) des définitions
5. Problème du modulo
---
## Pourquoi ?
* Cue 0.4 n'est pas thread safe -> bloque nos travaux de parallélisation des taches par zone
* Upgrade en Go 1.20
---
## Évolutions constatées
* Refonte du calcul des dépendances sur les taches flow
* Durcissement sur les struct close, il semble que le moteur avait des "tolérances", aujourd'hui il ne laisse rien passer
* Changement de l'affichage des float : précision à 1 chiffre après la virgule automatique
---
## Problème de dépendances des taches flow
----
### Rappel sur les tasks flow
* des "pre" et "post" task *->* moteur "Cue Flow"
* `$id` et `$after` *->* ❌ ~~moteur "Cue Flow"~~
:warning: Cue Flow n'est pas "Cue Tool.Task"
----
Avec trackbone, on charge ce qui se trouve sous les branches
* `run.pre_tasks`
* `run.pre_apply_tasks`
* `run.pre_plan_tasks`
* `run.pre_destroy_tasks`
* `run.post_tasks`
* `run.post_apply_tasks`
* `run.post_plan_tasks`
* `run.post_destroy_tasks`
----
### Problème dépendance `_cst_id`
Utilisé pour injecter des dépendances auto-magiquement pour les jobs d’un certain type.
C’est défini dans `custom_tasks.cue`
----
``` cue
#RDSDBSecret: {
vault.#Read
// Custom Task ID
_cst_id: "RDSDBSecret"
// Interface
namespace: string
// The values of the DB secret
data: {
// Applications should use the private IP
private_ip: string
port: string
// The DB name
name: string
username: string
password: string
ca: string
}
}
```
----
**`custom_tasks.cue`**
``` cue
run: [=~"_tasks$"]: [string]: {
_cst_id: *"" | string
if _cst_id == #RDSDBSecret._cst_id {
namespace: string
url: run.pre_tasks["vault_\(infra_zone.name)"].url
path: "secret/zones/\(zone.provider.type)/\(zone.name)/rds/\(namespace)/db"
}
}
```
----
### Problème
`if _cst_id == #RDSDBSecret._cst_id` semble poser problème au calcul des dépendances de Cue Flow et provoque des dépendances illogiques.
----
### Solution
Pour retirer ce `if` on utilise un suffixe sur le job
``` cue
run: [=~"_tasks$"]: [=~"_db$"]: #RDSDBSecret & {
namespace: string
url: run.pre_tasks["vault_\(infra_zone.name)"].url
path: "secret/zones/\(zone.provider.type)/\(zone.name)/rds/\(namespace)/db"
}
```
----
### Problème dépendance sur interpolation
Lors d’une interpolation, il semble ne pas toujours voir la dépendance.
``` cue
login: exec.#Run & {
cmd: ["nix-shell", "--quiet", "--run", """
az login -u \(secret.data.username) -p \(secret.data.password) --allow-no-subscriptions --output table
""",
]
}
```
__je n'ai pas toujours reproduit ce bug 🤷♂️__
----
### Solution
On extrait la dépendance pour la rendre explicite
``` cue
login: exec.#Run & {
let UserName = tasks.secret.data.username
let Password = tasks.secret.data.password
cmd: ["nix-shell", "--quiet", "--run", """
az login -u \(UserName) -p \(Password) --allow-no-subscriptions --output table
""",
]
}
```
----
### Problème dépendance sur une valeur concrète
Si la dépendance est liée à un élément concret, il ne sera pas vu comme dépendance de la tache.
```cue
url: pre_tasks["vault_infra"].url
```
`.url` est défini et ne dépend pas de l’exécution de la tache `vault_infra`.
donc notre tache ne dépend pas de `vault_infra`.
----
### Solution
Pour résoudre ce problème on va lier explicitement la tache `vault_infra` à notre tache.
``` cue
_vault_login: pre_tasks["vault_infra"]
url: _vault_login.url
```
----
### Exemple Boucle de dépendance
```mermaid
graph LR
t0("run.pre_tasks.vault_infra [Ready]")
t1("run.pre_tasks.write_environ [Waiting]")
t1-->t14
t2("run.pre_tasks.write_plan [Waiting]")
t2-->t8
t2-->t9
t2-->t17
t2-->t7
t3("run.pre_tasks.write_apply [Waiting]")
t3-->t8
t3-->t9
t3-->t17
t3-->t7
t4("run.pre_tasks.write_plan_destroy [Waiting]")
t4-->t8
t4-->t9
t4-->t17
t4-->t7
t5("run.pre_tasks.write_values [Waiting]")
t5-->t8
t5-->t9
t5-->t17
t5-->t7
t6("run.pre_tasks.write_plan_tpl [Ready]")
t7("run.pre_tasks.harbor_db [Waiting]")
t7-->t11
t7-->t14
t7-->t8
t7-->t9
t7-->t17
t7-->t5
t7-->t10
t7-->t13
t7-->t12
t7-->t0
t7-->t18
t8("run.pre_tasks.harbor_bucket [Waiting]")
t8-->t13
t8-->t14
t8-->t9
t8-->t17
t8-->t7
t8-->t5
t8-->t11
t8-->t10
t8-->t12
t8-->t0
t8-->t18
t9("run.pre_tasks.harbor_admin [Waiting]")
t9-->t13
t10("run.pre_tasks.#quot;vault_infra-stg_approle#quot; [Waiting]")
t10-->t0
t11("run.pre_tasks.#quot;vault_infra-stg#quot; [Waiting]")
t11-->t10
t12("run.pre_tasks.#quot;vault_ocb-test06_approle#quot; [Waiting]")
t12-->t0
t13("run.pre_tasks.#quot;vault_ocb-test06#quot; [Waiting]")
t13-->t12
t14("run.pre_tasks.kubeconfig [Ready]")
t15("run.pre_tasks.write_destroy [Waiting]")
t15-->t8
t15-->t9
t15-->t17
t15-->t7
t16("run.pre_tasks.write_plan_destroy_tpl [Ready]")
t17("run.pre_tasks.harbor_secrets [Waiting]")
t17-->t13
t18("run.pre_tasks.#quot;k8s_ocb-test06#quot; [Waiting]")
t18-->t14
t18-->t0
```
---
## Conséquences / Évolutions
----
### Évolution - Vault
```cue
url: pre_tasks["vault_\(zone.name)"].url
```
Devient
``` cue
_vault_login: pre_tasks["vault_\(zone.name)"]
url: _vault_login.url
```
----
**script**
``` shell
grep "vault.#Write" -rl | xargs \
sed -ri '/vault\.#Write/,+2 s/^(\s+)url:\s+(.*)\.url/\1_vault_login: \2\n\1url: _vault_login.url/'
grep "vault.#Read" -rl | xargs \
sed -ri '/vault\.#Read/,+2 s/^(\s+)url:\s+(.*)\.url/\1_vault_login: \2\n\1url: _vault_login.url/'
grep "vault.#List" -rl | xargs \
sed -ri '/vault\.#List/,+2 s/^(\s+)url:\s+(.*)\.url/\1_vault_login: \2\n\1url: _vault_login.url/'
grep "vault.#Delete" -rl | xargs \
sed -ri '/vault\.#Delete/,+2 s/^(\s+)url:\s+(.*)\.url/\1_vault_login: \2\n\1url: _vault_login.url/'
```
----
### Évolution - Kube Config
* Ajout du suffixe `_kubecfg` -> #KubectlAction
``` cue
[=~"_kubecfg$"]: {
$after: [for task_key, _ in run.pre_tasks if task_key =~ "^k8s_" {run.pre_tasks[task_key].$id}]
context: *zone.name | string
env: KUBECONFIG: run.pre_tasks.kubeconfig.abspath
}
```
Exemple
``` cue
run: pre_tasks: patchNS: #KubectlAction
// devient
run: pre_tasks: patchNS_kubecfg: #KubectlAction
```
----
### Évolution - DB
* Ajout du suffixe `_db` pour `#RDSDBSecret`
``` cue
[=~"_db$"]: #RDSDBSecret & {
_vault_login: run.pre_tasks["vault_\(infra_zone.name)"]
url: _vault_login.url
namespace: string
path: "secret/zones/\(zone.provider.type)/\(zone.name)/rds/\(namespace)/db"
}
```
Exemple
``` cue
let DB = run.pre_tasks.db.data
// devient
let DB = run.pre_tasks.rds_db.data
```
----
Autre Exemple
``` cue
admin_db_read: #RDSDBSecret & {namespace: "customers-manager"}
// devient
admin_read_db: #RDSDBSecret & {namespace: "customers-manager"}
```
----
### Évolution - Bucket
* Ajout du suffixe `_bucket` pour `#S3Secret`
``` cue
[=~"_bucket$"]: #S3Secret & {
namespace: string
if zone.product == "caascad" && zone.name != "corp-stg" {
_vault_login: run.pre_tasks["vault_\(zone.name)"]
url: _vault_login.url
}
if zone.name == "corp-stg" {
_vault_login: run.pre_tasks["vault_corp"]
url: _vault_login.url
}
path: *"secret/zones/\(zone.provider.type)/\(zone.name)/buckets/\(namespace)" | string
if zone.product == "caascad" {
path: "secret/bucket/\(namespace)"
}
}
```
----
Exemple
``` cue
let BucketUsages = run.pre_tasks.bucket_usages_read.data
// devient
let BucketUsages = run.pre_tasks.usages_read_bucket.data
```
---
## Problème de concurence avec Cue Flow
```
❯ trackbone plan -c fe_bastion -n8 -t staging
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x65206f]
```
_bug déjà présent sur cue 0.4_
----
#### Explication
Pour chaque tache, Cue Flow attribut un état, qui peut être `Waiting` -> `Ready` -> `Running` -> `Terminated`.
* Une tache qui dépend d'une autre est `Waiting`,
* Une tache qui n'a plus de dépendances (tous ses inputs sont concrets) est `Ready`
----
A chaque cycle Cue Flow
* écrit les outputs des taches `Terminated` dans le contexte Cue
* lit les inputs des taches `Ready` dans le même contexte Cue
* passe les taches `Ready` à `Running`
----
* Il crée 1 thread par tache `Running` (ce n'est pas configurable)
* Il écrit et lit dans le contexte Cue en même temps.
📛 Cue n'est pas thread safe 📛
----
### Solution (de contournement)
J'ai ajouté un mutex dans trackbone, pour réduire le nombre d'accès parallèles au contexte.
Mais comme flow fait également des écritures dans son coin
J'ai ajouté des wait pour lui laisser le temps de faire sa tambouille :see_no_evil:
---
## Rappel sur la fermeture (close) des définitions
----
### Quel est le résultat de ce code ?
``` cue
package test
#Test: {
a: string
}
#TEST2: {
#Test
b: string
}
#TEST3: #Test & {
b: string
}
```
----
### Réponse
```
#TEST3.b: field not allowed:
-:5:8
-:14:9
-:15:3
```
----
### Une structure est ouverte
On peut fermer une structure avec `close`
``` cue
ouvert: {
a: string
}
ferme: close({
a: string
})
```
----
### Une définition est fermée
Les préfixes `#` ou `_#` représente une définition qui est fermé
``` cue
#Ferme: {
a: string
}
#Ouvert: {
a: string
...
}
```
On peut ouvrir une définition avec l’ellipse `...`
----
### Fermer une structure
``` cue
#Ferme: {
a: string
}
ferme: #Ferme & {
a: "ok 🤘"
}
ouvert: {
#Ferme
b: string
}
```
---
## Problème du modulo
Changement de l'affichage des float
Précision à 1 chiffre après la virgule automatique
----
### Modulo ne retourne pas un entier 🤯
``` cue
let ThirdGroupByte = mod(div(RelativeClusterIndex, nbClusterPerGroup), 256)
let GroupCidr = "10.0.\(ThirdGroupByte).0/24"
```
Cue 0.4
GroupCidr = "10.0.128.0/24"
Cue 0.7
GroupCidr = "10.0.128.0.0/24"
*Non ce n’est pas IPv5*
----
### Explication
* En Go: toutes les fonctions de Math retournent un float
* Donc `mod`, `div`, etc en Cue retourne un float
----
#### En cue 0.4
L'interpolation retour la valeur la plus courte
"2.5 -> 2.5"
"2.0 -> 2"
"0 -> 0"
----
#### En cue 0.7
L'interpolation retour la valeur avec une précision de 1 nombre après la virgule (*il me semble*)
"2.5 -> 2.5"
"2.0 -> 2.0"
"0 -> 0.0"
----
### Solution
On fait la conversion en string manuellement
``` cue
let ThirdGroupByte = mod(div(RelativeClusterIndex, nbClusterPerGroup), 256)
let Str = strconv.FormatFloat(ThirdGroupByte, 102, 0, 64)
let GroupCidr = "10.0.\(Str).0/24"
```
###### Remarque:
la syntaxe normale c'est
strconv.FormatFloat(ThirdGroupByte, 'f', 0, 64)
mais le paramètre 'f' est transformé en bytes
au lieu de int à cause de cue.
---
## Conclusion / RAF
* Une régression peut en cacher une autre :train:
* Je recommande l'abandon de Cue Flow
* Garder Cue pour
* la validation de la configuration
* la génération des inputs pour les jobs
* le calcul des dépendances explicitement
----
### RAF
* Finalisation des tests
* Upgrade de env-ng et trackbone en simultanée bientôt 🤞
* Traitements parallélisés par zone pour réduire la conso mémoire