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