github.com/GoogleContainerTools/skaffold/v2@v2.13.2/docs-v2/design_proposals/lifecycle-hooks.md (about)

     1  # Design Proposal: 
     2  Skaffold lifecycle hooks 
     3  
     4  **authors:** Gaurav Ghosh (gaghosh@)  
     5  **status:** **approved**  
     6  **approval date:** 07-14-2020  
     7  **proposed on:** 07-06-2020  
     8  **approvers:**
     9  
    10  <table>
    11  <thead>
    12  <tr>
    13  <th><strong>LDAP</strong></th>
    14  <th><strong>LGTM Date</strong></th>
    15  </tr>
    16  </thead>
    17  <tbody>
    18  <tr>
    19  <td>bdealwis@</td>
    20  <td>2020-07-09</td>
    21  </tr>
    22  <tr>
    23  <td>nkubala@</td>
    24  <td>07/14/20</td>
    25  </tr>
    26  <tr>
    27  <td>tejaldesai@</td>
    28  <td>07/08/2020</td>
    29  </tr>
    30  </tbody>
    31  </table>
    32  
    33  # Background
    34  
    35  Supporting callbacks for lifecycle hooks is a heavily requested feature in the Skaffold community with some of the top voted issues being:
    36  
    37  1. [Issue #2425](https://github.com/GoogleContainerTools/skaffold/issues/2425): Users get away with wrapping skaffold in scripts that execute additional actions but want to move away from that 
    38  1. [Issue #3475](https://github.com/GoogleContainerTools/skaffold/issues/3475): Users want to execute tests prior to build 
    39  1. [Issue #1441](https://github.com/GoogleContainerTools/skaffold/issues/1441): Wrapping skaffold in a script doesn't solve the problem for iterative development using skaffold dev or debug where you'd want some custom action to repeat on every dev loop.
    40  1. [Issue #3737](https://github.com/GoogleContainerTools/skaffold/issues/3737): Users want to be able to calculate and export environment variables in the build step and reference it in subsequent steps. Currently there is no work around for this.
    41  
    42  In the past there have been discussions that prioritized implementing targeted features over a generic script execution that, while making the platform more flexible, renders it somewhat blind to the specific action that the user is trying to accomplish. However as evidenced by user feedback there are scenarios both in local development and CICD that can be readily solved by opening up hooks into Skaffold.
    43  
    44  # Overview
    45  
    46  There are three broad scenarios:
    47  
    48  1. Host hooks: Being able to execute a script on the host machine before and after every build/sync/deploy step.
    49  1. Container hooks: Being able to execute a script within a launched container after every sync or deploy step.
    50  1. Being able to export environment variables from a script executed as part of these lifecycle callbacks and reference them later at other steps. 
    51  
    52  # Detailed Design
    53  
    54  ## Schema
    55  
    56  ```yaml
    57  hooks:
    58     before:
    59       - command: [ “sleep”, “5”  ]
    60         os: [ “linux”, “darwin” ]
    61  
    62       - command: [ “timeout”, “5”  ]
    63         os: [ “windows” ]
    64  
    65       - containerCommand:  [ “echo”, “foo”  ]
    66         # containerName is optional for artifact scoped hooks like in build and sync
    67         containerName: foo
    68         # podPrefix is optional for artifact scoped hooks like in build and sync
    69         podPrefix: bar
    70  
    71     after:
    72       - command:  [ “echo”, “foo”  ]
    73  ```
    74  
    75  This can be nested under build, deploy and sync stages (described in examples below):
    76  
    77  -  Build: Hooks are defined per artifact build definition in skaffold.yaml
    78  -  Sync: Hooks are defined per artifact sync definition in skaffold.yaml
    79  -  Deploy: Hooks are defined per deployment type definition in skaffold.yaml
    80  
    81  Possible values for `os` field are all golang [recognised](https://github.com/golang/go/blob/master/src/go/build/syslist.go#L10) platforms. Missing value implies all.  
    82  If the command points to files, those don't get added to the change monitoring for dev loop. Inline commands by virtue of being part of the skaffold.yaml file would be subject to dev loop reload on change.
    83  
    84  ## Sample implementations 
    85  
    86  ### Example 1: Docker context composition
    87  
    88  For multiple microservices in a repo sharing multiple common libraries it may not be ideal for the repo root to be the Dockerfile context. We use pre-build hook to copy over the necessary files prior to the build
    89  
    90  ```yaml
    91  apiVersion: skaffold/vX
    92  kind: Config
    93  metadata:
    94   name: microservices
    95  
    96  build:
    97   artifacts:
    98     - image: leeroy-web
    99       context: ./leeroy-web/
   100       hooks:
   101         before:
   102           - command: [ “cp”, “./shared/package1.py”, “./leeroy-web/pkg/” ]
   103           - command: [ “./setup.sh” ]
   104  
   105     - image: leeroy-app
   106       context: ./leeroy-app/
   107  deploy: ...
   108  ```
   109  
   110  ### Example 2: Running tests
   111  
   112  We might want to run tests to validate artifacts or deployment. If it exits with non-zero status code then depending on the run type it'll stop the execution
   113  
   114  ```yaml
   115  apiVersion: skaffold/vX
   116  kind: Config
   117  metadata:
   118   name: microservices
   119  build:
   120   artifacts:
   121     - image: leeroy-web
   122       context: ./leeroy-web/
   123     - image: leeroy-app
   124       context: ./leeroy-app/
   125  
   126  deploy:
   127   kubectl:
   128     manifests:
   129       - ./leeroy-web/kubernetes/*
   130       - ./leeroy-app/kubernetes/*
   131     hooks:
   132       before:
   133         - command: [ “make”, “pre-deployment-tests”  ]
   134       after:
   135         - command: [ “make”, “post-deployment-tests”  ]
   136  ```
   137  
   138  ### Example 3: Run command on file sync
   139  
   140  We want to run say javascript minification post every file sync. 
   141  
   142  ```yaml
   143  apiVersion: skaffold/vX
   144  kind: Config
   145  metadata:
   146   name: node-example
   147  
   148  build:
   149   artifacts:
   150     - image: node-example
   151       context: ./node/
   152       sync: 
   153          manual:
   154            - src: ‘src/**/*.js’
   155            - dest: ‘./raw/’
   156          hooks: 
   157            after: 
   158               - container-command: [ “./minify-script.sh’, “./raw”, “./min/” ]
   159  
   160  deploy: ...
   161  ```
   162  
   163  ## Information contract
   164  
   165  <table>
   166  <thead>
   167  <tr>
   168  <th><strong>Environment variable</strong></th>
   169  <th><strong>Description</strong></th>
   170  <th><strong>Availability</strong></th>
   171  </tr>
   172  </thead>
   173  <tbody>
   174  <tr>
   175  <td>$IMAGE</td>
   176  <td>The fully qualified image name. For example, "gcr.io/image1:tag"</td>
   177  <td>Pre-Build; Post-Build</td>
   178  </tr>
   179  <tr>
   180  <td>$PUSH_IMAGE</td>
   181  <td>Set to true if the image in $IMAGE is expected to exist in a remote registry. Set to false if the image is expected to exist locally.</td>
   182  <td>Pre-Build; Post-Build</td>
   183  </tr>
   184  <tr>
   185  <td>$IMAGE_REPO</td>
   186  <td>The image repo. For example, "gcr.io/image1"</td>
   187  <td>Pre-Build; Post-Build</td>
   188  </tr>
   189  <tr>
   190  <td>$IMAGE_TAG</td>
   191  <td>The image tag. For example, "tag"</td>
   192  <td>Pre-Build; Post-Build</td>
   193  </tr>
   194  <tr>
   195  <td>$BUILD_CONTEXT</td>
   196  <td>An absolute path to the directory this artifact is meant to be built from. Specified by artifact context in the skaffold.yaml.</td>
   197  <td>Pre-Build; Post-Build</td>
   198  </tr>
   199  <tr>
   200  <td>$SYNC_FILES</td>
   201  <td>Semi-colon delimited list of absolute path to all files synced or to be synced in current dev loop</td>
   202  <td>Pre-Sync; Post-Sync</td>
   203  </tr>
   204  <tr>
   205  <td>$SKAFFOLD_RUN_ID</td>
   206  <td>Run specific UUID label for deployed or to be deployed resources</td>
   207  <td>Pre-Deploy; Post-Deploy</td>
   208  </tr>
   209  <tr>
   210  <td>$SKAFFOLD_DEFAULT_REPO</td>
   211  <td>The resolved default repository</td>
   212  <td>All</td>
   213  </tr>
   214  <tr>
   215  <td>$SKAFFOLD_RPC_PORT</td>
   216  <td>TCP port to expose event API</td>
   217  <td>All</td>
   218  </tr>
   219  <tr>
   220  <td>$SKAFFOLD_HTTP_PORT</td>
   221  <td>TCP port to expose event REST API over HTTP</td>
   222  <td>All</td>
   223  </tr>
   224  <tr>
   225  <td>$SKAFFOLD_KUBE_CONTEXT</td>
   226  <td>The resolved Kubernetes context</td>
   227  <td>All</td>
   228  </tr>
   229  <tr>
   230  <td>$SKAFFOLD_MULTI_LEVEL_REPO</td>
   231  <td>The multi-level support of the repository</td>
   232  <td>All</td>
   233  </tr>
   234  <tr>
   235  <td>$SKAFFOLD_NAMESPACES</td>
   236  <td>Comma separated list of Kubernetes namespaces</td>
   237  <td>All</td>
   238  </tr>
   239  <tr>
   240  <td>$SKAFFOLD_WORK_DIR</td>
   241  <td>The workspace root directory</td>
   242  <td>All</td>
   243  </tr>
   244  <tr>
   245  <td>$SKAFFOLD_PROFILES</td>
   246  <td>Comma separated list of activated profiles</td>
   247  <td>All</td>
   248  </tr>
   249  <tr>
   250  <td>Local environment variables</td>
   251  <td>The current state of the local environment (e.g. $HOST, $PATH). Determined by the golang <a href="https://golang.org/pkg/os#Environ">os.Environ</a> function.</td>
   252  <td>All</td>
   253  </tr>
   254  </tbody>
   255  </table>
   256  
   257  ## ENV propagation
   258  
   259  It might be helpful to be able to pass variables between multiple hooks callback functions. This can be achieved by setting a pattern to dump key-value pairs into standard output that are then parsed, stored and supplied in subsequent hooks command execution.
   260  
   261  For example, running:
   262  
   263  ```
   264  echo "::set-env name=FOO::BAR"
   265  ```
   266  
   267  will set the environment variable FOO to the value BAR
   268  
   269  ```yaml
   270  hooks:
   271     after:
   272         - command: [ “/bin/echo”, “::set-env”, “name=FOO::BAR”]
   273         - command: [ “/bin/echo”, “The value of FOO is ${FOO}”]
   274  ```
   275  
   276  This is only scoped to propagate values across lifecycle hooks commands only but across several dev loops.
   277  
   278  Note: There is a related issue [#4106](https://github.com/GoogleContainerTools/skaffold/issues/4106) requesting a feature of being able to read environment variables from files and substituting them in the skaffold template. If this feature is available then users can get away with modifying the environment variable file to propagate environment variables across stages without needing this implementation.
   279  
   280  # Testing
   281  
   282  In addition to regular unit tests we'll add integration tests against new example projects that showcase using hooks for the scenarios:
   283  
   284  1. Pre and post build
   285  1. Pre and post sync
   286  1. Pre and post deploy
   287  1. Env variable propagation across hooks
   288  
   289  # Metrics
   290  
   291  There are currently no metrics collected within skaffold. However events are reported on the event API server. We'll add event notifications for the following phases:
   292  
   293  <table>
   294  <thead>
   295  <tr>
   296  <th><strong>Event</strong></th>
   297  <th><strong>Params</strong></th>
   298  </tr>
   299  </thead>
   300  <tbody>
   301  <tr>
   302  <td>HostCallbackEvent</td>
   303  <td>Type(pre/post - build/sync/deploy)<br>
   304  Status(InProgress, Completed, Failed)<br>
   305  error message</td>
   306  </tr>
   307  <tr>
   308  <td>ContainerCallbackEvent</td>
   309  <td>Type(post - sync/deploy)<br>
   310  Status(InProgress, Completed, Failed)<br>
   311  error message</td>
   312  </tr>
   313  </tbody>
   314  </table>
   315  
   316  Additionally we'll append the count of each type of hooks defined in skaffold.yaml to the corresponding sections of the MetaEvent for Build, Sync and Deploy
   317  
   318  # 
   319  
   320  # Implementation breakdown
   321  
   322  <table>
   323  <thead>
   324  <tr>
   325  <th><strong>Priority</strong></th>
   326  <th><strong>Feature / Requirement</strong></th>
   327  <th><strong>Notes</strong></th>
   328  </tr>
   329  </thead>
   330  <tbody>
   331  <tr>
   332  <td></td>
   333  <td><u>Config changes</u></td>
   334  <td></td>
   335  </tr>
   336  <tr>
   337  <td>P0</td>
   338  <td>Users can define pre-hooks and post-hooks in the build, deploy and sync sections of skaffold.yaml</td>
   339  <td></td>
   340  </tr>
   341  <tr>
   342  <td></td>
   343  <td><u>Hooks Runner</u></td>
   344  <td></td>
   345  </tr>
   346  <tr>
   347  <td>P0</td>
   348  <td>Generic Host Hooks runner implemented and tested via unit tests</td>
   349  <td></td>
   350  </tr>
   351  <tr>
   352  <td>P0</td>
   353  <td>Generic Container Hooks runner implemented and tested via unit tests</td>
   354  <td></td>
   355  </tr>
   356  <tr>
   357  <td></td>
   358  <td><u>Dev loop integration</u></td>
   359  <td></td>
   360  </tr>
   361  <tr>
   362  <td>P0</td>
   363  <td>Runner integrated with pre and post build dev loop</td>
   364  <td></td>
   365  </tr>
   366  <tr>
   367  <td>P0</td>
   368  <td>Runner integrated with pre and post sync dev loop</td>
   369  <td></td>
   370  </tr>
   371  <tr>
   372  <td>P0</td>
   373  <td>Runner integrated with pre and post deploy dev loop</td>
   374  <td></td>
   375  </tr>
   376  <tr>
   377  <td>P0</td>
   378  <td>Integration examples with tests for all lifecycle hooks</td>
   379  <td></td>
   380  </tr>
   381  <tr>
   382  <td></td>
   383  <td><u>ENV propagation</u></td>
   384  <td></td>
   385  </tr>
   386  <tr>
   387  <td>P1</td>
   388  <td>Enable environment variable propagation across hooks</td>
   389  <td></td>
   390  </tr>
   391  <tr>
   392  <td>P1</td>
   393  <td>Integration example for env propagation</td>
   394  <td></td>
   395  </tr>
   396  </tbody>
   397  </table>
   398  
   399  # Alternatives Considered
   400  
   401  Schema Alternative 1 (too verbose, no explicit container-command)
   402  
   403  ```yaml
   404  hooks:
   405     pre:
   406       - exec:
   407           command: [ “sleep”, “5”  ]
   408           os: [ “linux”, “darwin” ]
   409       - exec:
   410           command:  [ “timeout”, “5”  ]
   411           os:  [ “windows” ]
   412  
   413     post:
   414       - exec:
   415           command:  [ “echo”, “foo”  ]
   416  ```
   417  
   418  Schema Alternative 2 (less verbose, dash-casing over camelCasing)
   419  
   420  ```yaml
   421  pre-hooks:
   422     - command: [ “sleep”, “5”  ]
   423       os: [ “linux”, “darwin” ]
   424  
   425     - command: [ “timeout”, “5”  ]
   426       os: [ “windows” ]
   427  
   428     - container-command:  [ “echo”, “foo”  ]
   429       # container-name is optional for artifact scoped hooks like in build and sync
   430       container-name: foo
   431       # pod-prefix is optional for artifact scoped hooks like in build and sync
   432       pod-prefix: bar
   433  
   434   post-hooks:
   435       - command:  [ “echo”, “foo”  ]
   436  
   437  ```