github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/README_DEVELOPERS.md (about)

     1  # Kuberpult Readme for developers
     2  
     3  ## Introduction
     4  
     5  Unlike ArgoCD, Kuberpult is not triggered based on push to the repository. It is triggered by REST api instead (or ui which, in turn, calls the REST api).
     6  When a `/release` endpoint is called with the manifest files, it checks the repository for additional information (ArgoCD related), then commits and pushes the manifests to the repository which is then handled by ArgoCD.
     7  For full usage instructions, please check the [readme](https://github.com/freiheit-com/kuberpult/blob/main/readme.md).
     8  
     9  ## Install dev tools
    10  It is split into two parts. The backend logic is in the `cd-service`. The frontend is also split into two parts (but they are both deployed as one microservice), the `frontend-service` that provides the REST backing for the ui, and the `ui-service` with the actual ui.
    11  The `cd-service` takes the URL of the repository to watch from the environment variable `KUBERPULT_GIT_URL` and the branch to watch from the environment variable `KUBERPULT_GIT_BRANCH`.
    12  
    13  ## Prerequisite software
    14  
    15  - [docker](https://docs.docker.com/get-docker/)
    16  - [docker-compose](https://docs.docker.com/compose/install/) v1.29.2
    17  
    18  ## Setup builder image
    19  
    20  You need a `builder` image that is tagged as `latest` to build services locally.
    21  The following command should do this for you.
    22  * `make builder` 
    23  
    24  There's no need to push the image.
    25  
    26  ## Setup and run instructions (with docker-compose)
    27  
    28  - in `services/cd-service`, initialize a bare repository with the name `repository_remote`
    29  
    30  ```bash
    31  cd services/cd-service
    32  git init --bare repository_remote
    33  cd ../..
    34  ```
    35  - This repository is bare, to populate it, fill it with data as described in `README.md` or https://github.com/freiheit-com/kuberpult/pull/95
    36  - the value of environment variables are defaulted to `KUBERPULT_GIT_URL=./repository_remote` and `KUBERPULT_GIT_BRANCH=master`
    37  - run the following command to start all the services required.
    38  ```bash
    39  make kuberpult
    40  ```
    41  
    42  For details on how to fill the repo, see the
    43  [Readme for testdata](infrastructure/scripts/create-testdata/Readme.md)
    44  
    45  - the `cd-service` is available at `localhost:8080`. And Kuberpult ui is available at `localhost:3000`
    46  
    47  ## Build and run kuberpult with Earthly
    48  - Download [Earthly](https://github.com/earthly/earthly/releases) binary and add it to your PATH.
    49  - In the root of the repository run `make kuberpult-earthly`. This will build the services (frontend/cd/ui) in a containerised environment and run docker-compose using the built images.
    50  ## GRCP Calls (with docker-compose setup)
    51  
    52  Most calls can be made directly from the UI.
    53  To make specific calls for manual testing, install [evans](https://github.com/ktr0731/evans).
    54  
    55  When the services are running with `docker-compose`, start evans like this:
    56  
    57  `evans --host localhost --port 8443  -r`
    58  
    59  ```
    60  header author-name=YXV0aG9y
    61  header author-email=YXV0aG9yQGF1dGhvcg==
    62  package api.v1
    63  service DeployService
    64  ```
    65  
    66  ```
    67  api.v1.DeployService@localhost:8443> call Deploy
    68  environment (TYPE_STRING) => development
    69  application (TYPE_STRING) => app-alerting-service
    70  version (TYPE_UINT64) => 91
    71  ignoreAllLocks (TYPE_BOOL) => false
    72  ✔ Queue
    73  {}
    74  ```
    75  
    76  
    77  #### Why the author headers?
    78  
    79  With a recent change, the cd-service now always expect author headers to be set, both in grpc and http endpoints.
    80  `/release` is the exception to that, but it logs a warning, when there is no author.
    81  (And of course `/health` is another exception).
    82  The frontend-service is now the only point that knows about default-author (see helm chart `git.author.name` & `git.author.email`).
    83  The frontend-service can be called with headers, then those will be used. If none are found, we use the default headers from the helm chart.
    84  
    85  ### Test that setup was done correctly
    86  
    87  - for adding changes and testing releasing, clone the `repository_remote` folder.
    88  - calling curl command to `/release` api with form data for the manifest file should have updated the remote repository with a new release.
    89  - view the changes in ui as well
    90  
    91  ```bash
    92  cd services/cd-service
    93  git clone ./repository_remote repository_checkedout
    94  cd repository_checkedout
    95  touch manifest.yaml
    96  # This should cause the release to be pushed to the git repository
    97  curl --form-string 'application=helloworld' --form 'manifests[development]=@manifest.yaml' localhost:8080/release
    98  git pull
    99  cd ../../..
   100  ```
   101  
   102  ## Unit tests
   103  
   104  Go tests would be part of the same package as the main code, but ending the file names with `_test.go`. When adding new test cases, please use [table driven tests](https://revolution.dev/app/-JqFGExX46gs9mH7vxR5/WORKSPACE_DOCUMENT/-MjkBXy5_eugWYQsxyHl/)
   105  
   106  To run tests, the root makefile has the test command, which runs the test commands in `services/cd-service/Makefile` and `services/frontend-service/Makefile`, which, in turn, run tests for go and pnpm files.
   107  
   108  ```bash
   109  make test
   110  ```
   111  
   112  When there are build issues in the test code, it will show up as a build failure during make test with the proper error.
   113  
   114  When a single test case fails, the test case shows up with the corresponding error.
   115  
   116  For a more verbose version, you could go into the service directory and run the tests manually in verbose mode.
   117  
   118  ```bash
   119  cd services/cd-service
   120  go test ./... -v
   121  ```
   122  
   123  ### Best practices for unit tests
   124  
   125  #### Always use `cmp.Diff`
   126  
   127  When writing unit tests, aim to always compare with `cmp.Diff` and print the result.
   128  For errors, the test should check via:
   129  ```go
   130  _, err := unitUnderTest(…)
   131  if diff := cmp.Diff(testcase.ExpectedError, err, cmpopts.EquateErrors()); diff != "" {
   132    t.Errorf("error mismatch (-want, +got):\n%s", diff)
   133  }
   134  ```
   135  For proto-messages, we need to use `protocmp.Transform()`
   136  ```go
   137  if diff := cmp.Diff(testcase.ExpectedResponse, gotResponse, protocmp.Transform()); diff != "" {
   138    t.Errorf("response mismatch (-want, +got):\n%s", diff)
   139  }
   140  ```
   141  
   142  #### Do not rely on verbatim JSON
   143  
   144  Tests should not rely on the actual JSON representation of objects, but rather compare actual objects, even if it is more verbose.
   145  As the representation generated by protojson is not stable, this would otherwise lead to flaky tests.
   146  
   147  **Bad**:
   148  ```go
   149  testCase := TestCase{
   150    …
   151    expectedErrorMsg: `error at index 2 of transformer batch: already_exists_different:{first_differing_field:MANIFESTS  diff:"--- acceptance-existing\n+++ acceptance-request\n@@ -1 +1 @@\n-{}\n\\ No newline at end of file\n+{ \"different\": \"yes\" }\n\\ No newline at end of file\n"}`,
   152    …
   153  }
   154  ```
   155  **Good**:
   156  ```go
   157  testCase := TestCase{
   158    …
   159    expectedError: &TransformerBatchApplyError{
   160      Index: 2,
   161      TransformerError: &CreateReleaseError{
   162        response: api.CreateReleaseResponse{
   163          Response: &api.CreateReleaseResponse_AlreadyExistsDifferent{
   164            AlreadyExistsDifferent: &api.CreateReleaseResponseAlreadyExistsDifferent{
   165              FirstDifferingField: api.DifferingField_MANIFESTS,
   166              Diff:                "--- acceptance-existing\n+++ acceptance-request\n@@ -1 +1 @@\n-{}\n\\ No newline at end of file\n+{ \"different\": \"yes\" }\n\\ No newline at end of file\n",
   167            },
   168          },
   169        },
   170      },
   171    },
   172    …
   173  }
   174  ```
   175  
   176  # Installation outside of docker
   177  
   178  ## Podman and podman-compose
   179  If you use Podman (and `podman-compose`) instead (e.g. on macOS), you might need to specify `user: 0` for **each** container.
   180  because otherwise the process in the container does not have access to the filesystem mounted from the user's home directory into the container.
   181  Using UID 0 should be fine with Podman, as it (unlike Docker) runs the container with the privileges of the current user (the UID of the current user is mapped to UID 0 inside the container). e.g.:
   182  ```
   183  backend:
   184      build: infrastructure/docker/backend
   185      container_name: kuberpult-cd-service
   186      ports:
   187        - "8080:8080"
   188        - "8443:8443"
   189  >>> user: 0
   190      volumes:
   191        - .:/kp/kuberpult
   192  ```
   193  ## prerequisite software
   194  
   195  - [docker](https://docs.docker.com/get-docker/) - for docker build for cd-service - optional
   196  - [node](https://nodejs.org/en/download/) - ensure you're using an LTS version (or use [nvm](https://github.com/nvm-sh/nvm#installing-and-updating))
   197  Ideally use the same version as in the [package.json](https://github.com/freiheit-com/kuberpult/blob/main/services/frontend-service/package.json#L42)
   198  - [pnpm](https://pnpm.io/installation)
   199  
   200  ## Libraries required
   201  - libgit2 >= 1.0
   202    download tar file and follow instructions here: https://github.com/libgit2/libgit2#installation
   203    it worked for me to run: (the instructions are slightly different)
   204    ```
   205    sudo apt-get install libssl-dev
   206    mkdir build && cd build
   207    cmake -DUSE_SSH=ON ..
   208    sudo cmake --build . --target install
   209    ```
   210    Afterwards, set your library path, e.g.: `export LD_LIBRARY_PATH='/usr/local/lib/'`
   211  
   212    For m1 mac:
   213    brew and macports don't have the version of libgit2 that we need (1.3.0)
   214    so what we do is we install macports, then travel back in timie to when 1.3.0 was the latest and then install it.
   215  
   216    - install macports from [official site](https://www.macports.org/install.php)
   217    - install libgit2
   218  
   219    ```
   220  # Get macports repo
   221  git clone https://github.com/macports/macports-ports.git
   222  cd macports-ports
   223  # Travel back in time to when libgit2 version was 1.3.0
   224  git checkout b2b896fb904cfd14d8d6f3063c0b620b52b94f31
   225  # Install libgit2
   226  cd devel/libgit2
   227  sudo port install 
   228  # Convince package config that we do infact have libgit2 (change to rc file of whichever shell you use)
   229  echo "export PKG_CONFIG_PATH=/opt/local/lib/pkgconfig" >> ~/.zshrc
   230  source ~/.zshrc
   231    ```
   232  
   233  -  libsqlite3
   234  
   235  On ubuntu: install the apt package `libsqlite3-dev`
   236  On mac: install the macports package `sqlite3`
   237  
   238  
   239  - Chart Testing:
   240    - install `helm`, `Yamale`, `Yamllint` as prerequisites to `ct` from https://github.com/helm/chart-testing#installation
   241    - then follow the instructions to install `ct`
   242  - golang >= 1.16
   243  - protoc >=3.15
   244  - buf from https://docs.buf.build/installation
   245  
   246  ## Setup and Run
   247  
   248  ### With makefiles
   249  
   250  - in `services/cd-service`, initialize a bare repository with the name `repository_remote`
   251  
   252  ```bash
   253  cd services/cd-service
   254  git init --bare repository_remote
   255  ```
   256  
   257  To run the services: `make kuberpult`
   258  
   259  
   260  ## releasing a new version
   261  
   262  Releases are half-automated via GitHub actions.
   263  
   264  Go to the [release workflow pipeline](https://github.com/freiheit-com/kuberpult/actions/workflows/release.yml) and trigger "run pipeline" on the main branch.
   265  
   266  ## Changelog generation and semantic versioning
   267  Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) to
   268  make your changes show up in the changelog. In short:
   269  * `fix` will create a `PATCH` level semantic version
   270  * `feat` will create `MINOR` level semantic version
   271  * adding a `!` will mark a breaking change and create a `MAJOR` level semantic version
   272  
   273  In addition to `fix`, `feat` and breaking changes, the following [types](https://github.com/go-semantic-release/changelog-generator-default/blob/master/pkg/generator/changelog_types.go#L32) can be considered, but are currently **not** allowed in kuberpult:
   274  * revert
   275  * perf
   276  * docs
   277  * test
   278  * refactor
   279  * style
   280  * chore
   281  * build
   282  * ci
   283  
   284  The changelog and the version is generated with
   285  [go-semantic-release](https://go-semantic-release.xyz/) via its
   286  [github action](https://github.com/go-semantic-release/action) and it generates the changelogs with the
   287  [default generator](https://github.com/go-semantic-release/changelog-generator-default).
   288  
   289  ## Notes
   290  
   291  - there is a dev image based on alpine in `docker/build`. You can start a shell in the image using the `./dmake` command.
   292  
   293  - The first version of this tool was written using go-git v5. Sadly the performance was abysmal. Adding a new manifest took > 20 seconds. Therefore, we switched to libgit2, which is much faster but less ergonomic.
   294  
   295  ## Running locally with 2 images
   296  The normal docker-compose.yml file starts 3 containers: cd-service, frontend-service, ui.
   297  The file `docker-compose.tpl.yml` starts 2 containers: cd-service and frontend+ui in one.
   298  In the helm chart, there are also only 2 containers.
   299  
   300  Pros of running 2 containers:
   301  * closer to the "real world", meaning the helm chart
   302  * You can (manually) test things like path redirects much better
   303  
   304  Cons of running 2 containers:
   305  * There's no UI hot-reload
   306  
   307  To run with 2 containers (you need to run this with every change):
   308  ```shell
   309  # replace "sven" with any other prefix or your choice:
   310  docker-compose stop
   311  PREFIX=sven-e
   312  VERSION=$(git describe --always --long --tags)
   313  export IMAGE_REGISTRY=europe-west3-docker.pkg.dev/fdc-public-docker-registry/kuberpult 
   314  IMAGENAME="$IMAGE_REGISTRY"/kuberpult-cd-service:"$PREFIX"-"$VERSION" make docker -C services/cd-service/
   315  IMAGENAME="$IMAGE_REGISTRY"/kuberpult-frontend-service:"$PREFIX"-"$VERSION" make docker -C services/frontend-service/
   316  IMAGE_TAG_CD="$PREFIX"-"$VERSION" IMAGE_TAG_FRONTEND="$PREFIX"-"$VERSION" dc -f ./docker-compose.tpl.yml up -d --remove-orphans
   317  ```
   318  Now open a browser to `http://localhost:8081/`.
   319