|
| 1 | +load('ext://restart_process', 'docker_build_with_restart') |
| 2 | +load('ext://cert_manager', 'deploy_cert_manager') |
| 3 | + |
| 4 | + |
| 5 | +def deploy_cert_manager_if_needed(): |
| 6 | + cert_manager_var = '__CERT_MANAGER__' |
| 7 | + if os.getenv(cert_manager_var) != '1': |
| 8 | + deploy_cert_manager(version="v1.12.3") |
| 9 | + os.putenv(cert_manager_var, '1') |
| 10 | + |
| 11 | + |
| 12 | +# Set up our build helper image that has delve in it. We use a helper so parallel image builds don't all simultaneously |
| 13 | +# install delve. Instead, they all wait for this build to complete, and then proceed in parallel. |
| 14 | +docker_build( |
| 15 | + ref='helper', |
| 16 | + context='.', |
| 17 | + build_args={'GO_VERSION': '1.20'}, |
| 18 | + dockerfile_contents=''' |
| 19 | +ARG GO_VERSION |
| 20 | +FROM golang:$GO_VERSION |
| 21 | +RUN CGO_ENABLED=0 go install github.com/go-delve/delve/cmd/dlv@v$GO_VERSION |
| 22 | +''' |
| 23 | +) |
| 24 | + |
| 25 | + |
| 26 | +def build_binary(repo, binary, image, debug=True): |
| 27 | + gcflags = '' |
| 28 | + if debug: |
| 29 | + gcflags = "-gcflags 'all=-N -l'" |
| 30 | + |
| 31 | + # Treat the main binary as a local resource, so we can automatically rebuild it when any of the deps change. This |
| 32 | + # builds it locally, targeting linux, so it can run in a linux container. |
| 33 | + local_resource( |
| 34 | + '{}_{}_binary'.format(repo, binary), |
| 35 | + cmd=''' |
| 36 | +cd ../{repo} |
| 37 | +mkdir -p .tiltbuild/bin |
| 38 | +CGO_ENABLED=0 GOOS=linux go build {gcflags} -o .tiltbuild/bin/{binary} ./cmd/{binary} |
| 39 | +'''.format(repo=repo, binary=binary, gcflags=gcflags), |
| 40 | + deps=['api', 'cmd/{}'.format(binary), 'internal', 'pkg', 'go.mod', 'go.sum'] |
| 41 | + ) |
| 42 | + |
| 43 | + entrypoint = '/{}'.format(binary) |
| 44 | + if debug: |
| 45 | + entrypoint = '/dlv --accept-multiclient --api-version=2 --headless=true --listen :30000 exec --continue -- ' + entrypoint |
| 46 | + |
| 47 | + # Configure our image build. If the file in live_update.sync (.tiltbuild/bin/$binary) changes, Tilt |
| 48 | + # copies it to the running container and restarts it. |
| 49 | + docker_build_with_restart( |
| 50 | + # This has to match an image in the k8s_yaml we call below, so Tilt knows to use this image for our Deployment, |
| 51 | + # instead of the actual image specified in the yaml. |
| 52 | + ref='{image}:{binary}'.format(image=image, binary=binary), |
| 53 | + # This is the `docker build` context, and because we're only copying in the binary we've already had Tilt build |
| 54 | + # locally, we set the context to the directory containing the binary. |
| 55 | + context='../{}/.tiltbuild/bin'.format(repo), |
| 56 | + # We use a slimmed-down Dockerfile that only has $binary in it. |
| 57 | + dockerfile_contents=''' |
| 58 | +FROM gcr.io/distroless/static:debug |
| 59 | +WORKDIR / |
| 60 | +COPY --from=helper /go/bin/dlv / |
| 61 | +COPY {} / |
| 62 | + '''.format(binary), |
| 63 | + # The set of files Tilt should include in the build. In this case, it's just the binary we built above. |
| 64 | + only=binary, |
| 65 | + # If .tiltbuild/bin/$binary changes, Tilt will copy it into the running container and restart the process. |
| 66 | + live_update=[ |
| 67 | + sync('.tiltbuild/bin/{}'.format(binary), '/{}'.format(binary)), |
| 68 | + ], |
| 69 | + # The command to run in the container. |
| 70 | + entrypoint=entrypoint, |
| 71 | + ) |
| 72 | + |
| 73 | + |
| 74 | +def process_yaml(yaml): |
| 75 | + if type(yaml) == 'string': |
| 76 | + objects = read_yaml_stream(yaml) |
| 77 | + elif type(yaml) == 'blob': |
| 78 | + objects = decode_yaml_stream(yaml) |
| 79 | + else: |
| 80 | + fail('expected a string or blob, got: {}'.format(type(yaml))) |
| 81 | + |
| 82 | + for o in objects: |
| 83 | + # For Tilt's live_update functionality to work, we have to run the container as root. Remove any PSA labels |
| 84 | + # to allow this. |
| 85 | + if o['kind'] == 'Namespace' and 'labels' in o['metadata']: |
| 86 | + labels_to_delete = [label for label in o['metadata']['labels'] if label.startswith('pod-security.kubernetes.io')] |
| 87 | + for label in labels_to_delete: |
| 88 | + o['metadata']['labels'].pop(label) |
| 89 | + |
| 90 | + if o['kind'] != 'Deployment': |
| 91 | + # We only need to modify Deployments, so we can skip this |
| 92 | + continue |
| 93 | + |
| 94 | + # For Tilt's live_update functionality to work, we have to run the container as root. Otherwise, Tilt won't |
| 95 | + # be able to untar the updated binary in the container's file system (this is how live update |
| 96 | + # works). If there are any securityContexts, remove them. |
| 97 | + if "securityContext" in o['spec']['template']['spec']: |
| 98 | + o['spec']['template']['spec'].pop('securityContext') |
| 99 | + for c in o['spec']['template']['spec']['containers']: |
| 100 | + if "securityContext" in c: |
| 101 | + c.pop('securityContext') |
| 102 | + |
| 103 | + # If multiple Deployment manifests all use the same image but use different entrypoints to change the binary, |
| 104 | + # we have to adjust each Deployment to use a different image. Tilt needs each Deployment's image to be |
| 105 | + # unique. We replace the tag with what is effectively :$binary, e.g. :helm. |
| 106 | + for c in o['spec']['template']['spec']['containers']: |
| 107 | + if c['name'] == 'kube-rbac-proxy': |
| 108 | + continue |
| 109 | + |
| 110 | + command = c['command'][0] |
| 111 | + if command.startswith('./'): |
| 112 | + command = command.removeprefix('./') |
| 113 | + elif command.startswith('/'): |
| 114 | + command = command.removeprefix('/') |
| 115 | + |
| 116 | + image_without_tag = c['image'].rsplit(':', 1)[0] |
| 117 | + |
| 118 | + # Update the image so instead of :$tag it's :$binary |
| 119 | + c['image'] = '{}:{}'.format(image_without_tag, command) |
| 120 | + |
| 121 | + # Now apply all the yaml |
| 122 | + k8s_yaml(encode_yaml_stream(objects)) |
| 123 | + |
| 124 | + |
| 125 | +# data format: |
| 126 | +# { |
| 127 | +# 'image': 'quay.io/operator-framework/rukpak', |
| 128 | +# 'yaml': 'manifests/overlays/cert-manager', |
| 129 | +# 'binaries': { |
| 130 | +# 'core': 'core', |
| 131 | +# 'crdvalidator': 'crd-validation-webhook', |
| 132 | +# 'helm': 'helm-provisioner', |
| 133 | +# 'webhooks': 'rukpak-webhooks', |
| 134 | +# }, |
| 135 | +# }, |
| 136 | +def deploy_repo(repo, data): |
| 137 | + print('Deploying repo {}'.format(repo)) |
| 138 | + deploy_cert_manager_if_needed() |
| 139 | + |
| 140 | + local_port = data['starting_debug_port'] |
| 141 | + for binary, deployment in data['binaries'].items(): |
| 142 | + build_binary(repo, binary, data['image']) |
| 143 | + k8s_resource(deployment, port_forwards=['{}:30000'.format(local_port)]) |
| 144 | + local_port += 1 |
| 145 | + process_yaml(kustomize('../{}/{}'.format(repo, data['yaml']))) |
0 commit comments