Skip to content

Commit e86a23d

Browse files
sgaistolevski
andauthored
feat: repository cloner
This feature allows users to specify a list of repositories to clone when starting their environment. Each repository can come from a different provider and use different protocols (e.g. https, git). Username + password or certificate + unlock password can be used to clone private repositories. --------- Co-authored-by: Tasko Olevski <[email protected]>
1 parent 4f03aea commit e86a23d

13 files changed

+761
-7
lines changed

api/v1alpha1/amaltheasession_children.go

+56-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ func (cr *AmaltheaSession) StatefulSet() appsv1.StatefulSet {
8686
)
8787
}
8888

89+
initContainers := []v1.Container{}
90+
91+
if len(cr.Spec.CodeRepositories) > 0 {
92+
gitCloneContainers, gitCloneVols := cr.initClones()
93+
initContainers = append(initContainers, gitCloneContainers...)
94+
volumes = append(volumes, gitCloneVols...)
95+
}
96+
97+
initContainers = append(initContainers, cr.Spec.ExtraInitContainers...)
98+
8999
// NOTE: ports on a container are for information purposes only, so they are removed because the port specified
90100
// in the CR can point to either the session container or another container.
91101
sessionContainer := v1.Container{
@@ -197,7 +207,7 @@ func (cr *AmaltheaSession) StatefulSet() appsv1.StatefulSet {
197207
},
198208
Spec: v1.PodSpec{
199209
Containers: containers,
200-
InitContainers: cr.Spec.ExtraInitContainers,
210+
InitContainers: initContainers,
201211
Volumes: volumes,
202212
},
203213
},
@@ -354,6 +364,51 @@ func (cr *AmaltheaSession) Pod(ctx context.Context, clnt client.Client) (*v1.Pod
354364
return &pod, err
355365
}
356366

367+
// Generates the init containers that clones the specified Git repositories
368+
func (cr *AmaltheaSession) initClones() ([]v1.Container, []v1.Volume) {
369+
envVars := []v1.EnvVar{}
370+
volMounts := []v1.VolumeMount{{Name: sessionVolumeName, MountPath: cr.Spec.Session.Storage.MountPath}}
371+
vols := []v1.Volume{}
372+
containers := []v1.Container{}
373+
374+
for irepo, repo := range cr.Spec.CodeRepositories {
375+
args := []string{"clone", "--remote", repo.Remote, "--path", cr.Spec.Session.Storage.MountPath + "/" + repo.ClonePath}
376+
377+
if repo.CloningConfigSecretRef != nil {
378+
secretVolName := fmt.Sprintf("git-clone-cred-volume-%d", irepo)
379+
secretMountPath := "/git-clone-secrets"
380+
secretFilePath := fmt.Sprintf("%s/%s", secretMountPath, repo.CloningConfigSecretRef.Key)
381+
vols = append(
382+
vols,
383+
v1.Volume{
384+
Name: secretVolName,
385+
VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: repo.CloningConfigSecretRef.Name}},
386+
},
387+
)
388+
volMounts = append(volMounts, v1.VolumeMount{Name: secretVolName, MountPath: secretMountPath})
389+
390+
args = append(args, []string{"--config", secretFilePath}...)
391+
}
392+
393+
if repo.Revision != "" {
394+
args = append(args, []string{"--revision", repo.Revision}...)
395+
}
396+
397+
gitCloneContainerName := fmt.Sprintf("git-clone-%d", irepo)
398+
containers = append(containers, v1.Container{
399+
Name: gitCloneContainerName,
400+
Image: "renku/cloner:0.0.1",
401+
VolumeMounts: volMounts,
402+
WorkingDir: cr.Spec.Session.Storage.MountPath,
403+
Env: envVars,
404+
SecurityContext: &v1.SecurityContext{RunAsUser: &cr.Spec.Session.RunAsUser, RunAsGroup: &cr.Spec.Session.RunAsGroup},
405+
Args: args,
406+
})
407+
}
408+
409+
return containers, vols
410+
}
411+
357412
// Returns the list of all the secrets used in this CR
358413
func (cr *AmaltheaSession) AllSecrets() v1.SecretList {
359414
secrets := v1.SecretList{}

api/v1alpha1/amaltheasession_types.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type AmaltheaSessionSpec struct {
3131
Session Session `json:"session"`
3232

3333
// +optional
34+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="CodeRepositories is immutable"
3435
// A list of code repositories and associated configuration that will be cloned in the session
3536
CodeRepositories []CodeRepository `json:"codeRepositories,omitempty"`
3637

@@ -160,13 +161,16 @@ type CodeRepository struct {
160161
// The tag, branch or commit SHA to checkout, if omitted then will be the tip of the default branch of the repo
161162
Revision string `json:"revision,omitempty"`
162163
// The Kubernetes secret that contains the code repository configuration to be used during cloning.
163-
// For 'git' this is the git configuration which can be used to inject credentials in addition to any other repo-specific Git configuration.
164+
// For 'git' this should contain either:
165+
// The username and password
166+
// The private key and its corresponding password
167+
// An empty value can be used when cloning from public repositories using the http protocol
164168
// NOTE: you have to specify the whole config in a single key in the secret.
165-
CloningConfigSecretRef *SessionSecretRef `json:"cloningGitConfigSecretRef,omitempty"`
169+
CloningConfigSecretRef *SessionSecretRef `json:"cloningConfigSecretRef,omitempty"`
166170
// The Kubernetes secret that contains the code repository configuration to be used when the session is running.
167171
// For 'git' this is the git configuration which can be used to inject credentials in addition to any other repo-specific Git configuration.
168172
// NOTE: you have to specify the whole config in a single key in the secret.
169-
ConfigSecretRef *SessionSecretRef `json:"gitConfigSecretRef,omitempty"`
173+
ConfigSecretRef *SessionSecretRef `json:"configSecretRef,omitempty"`
170174
}
171175

172176
// +kubebuilder:validation:Enum={rclone}

cloner/.gitignore

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#
2+
# Copyright 2024.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
#
18+
# Binaries for programs and plugins
19+
*.exe
20+
*.exe~
21+
*.dll
22+
*.so
23+
*.dylib
24+
25+
# Test binary, built with `go test -c`
26+
*.test
27+
28+
# Output of the go coverage tool, specifically when used with LiteIDE
29+
*.out
30+
31+
# Dependency directories (remove the comment below to include it)
32+
# vendor/
33+
34+
# Go workspace file
35+
go.work
36+
go.work.sum
37+
38+
# env file
39+
.env
40+
41+
# Output folder
42+
bin/

cloner/Dockerfile

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Build the application from source
2+
FROM golang:1.22 AS build-stage
3+
4+
WORKDIR /app
5+
6+
COPY go.mod go.sum ./
7+
RUN go mod download
8+
9+
COPY *.go ./
10+
COPY cmd ./cmd
11+
12+
RUN CGO_ENABLED=0 GOOS=linux go build -o /cloner
13+
14+
# Run the tests in the container
15+
FROM build-stage AS run-test-stage
16+
RUN go test -v ./...
17+
18+
# Deploy the application binary into a lean image
19+
FROM gcr.io/distroless/base-debian12 AS build-release-stage
20+
21+
WORKDIR /
22+
23+
COPY --from=build-stage /cloner /cloner
24+
25+
USER nonroot:nonroot
26+
27+
ENTRYPOINT ["/cloner"]

cloner/Makefile

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#
2+
# Copyright 2024.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
# VERSION defines the project version for the bundle.
18+
# Update this value when you upgrade the version of your project.
19+
# To re-generate a bundle for another specific version without changing the standard setup, you can:
20+
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
21+
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
22+
VERSION ?= 0.0.1
23+
LDFLAGS="-X 'cloner/cmd.Version=v$(VERSION)'"
24+
25+
# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
26+
# This variable is used to construct full image tags for bundle and catalog images.
27+
#
28+
# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
29+
# amalthea.dev/amalthea-bundle:$VERSION and amalthea.dev/amalthea-catalog:$VERSION.
30+
IMAGE_TAG_BASE ?= renku/cloner
31+
IMG ?= $(IMAGE_TAG_BASE):$(VERSION)
32+
33+
# CONTAINER_TOOL defines the container tool to be used for building images.
34+
# Be aware that the target commands are only tested with Docker which is
35+
# scaffolded by default. However, you might want to replace it to use other
36+
# tools. (i.e. podman)
37+
CONTAINER_TOOL ?= docker
38+
39+
# Setting SHELL to bash allows bash commands to be executed by recipes.
40+
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
41+
SHELL = /usr/bin/env bash -o pipefail
42+
.SHELLFLAGS = -ec
43+
44+
.PHONY: help
45+
help: ## Display this help.
46+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
47+
48+
.PHONY: all
49+
all: mod test vet fmt build run
50+
51+
##@ Run
52+
53+
.PHONY:
54+
run: fmt vet build run ## Run the proxy
55+
chmod +x bin/cloner
56+
./bin/cloner
57+
58+
.PHONY: install
59+
install: run ## Install the proxy
60+
go install -v ./...
61+
62+
##@ QA
63+
64+
.PHONY: audit
65+
audit: ## Run quality control checks
66+
go mod verify
67+
go vet ./...
68+
go run honnef.co/go/tools/cmd/staticcheck@latest -checks=all,-ST1000,-U1000 ./...
69+
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
70+
71+
.PHONY: test
72+
test: build ## Run tests
73+
go test $$(go list ./... | grep -v /e2e) -race -buildvcs
74+
75+
.PHONY: vet
76+
vet: ## Run go vet against code.
77+
go vet ./...
78+
79+
.PHONY: fmt
80+
fmt: ## Run go fmt against code.
81+
go fmt ./...
82+
83+
.PHONY: mod
84+
mod: ## Tidy mods
85+
go mod tidy
86+
87+
.PHONY: code-cleanup
88+
code-cleanup: vet fmt mod ## Code cleanup
89+
90+
##@ Build
91+
92+
.PHONY: build
93+
build: fmt vet
94+
go build -ldflags=$(LDFLAGS) -o bin/cloner main.go
95+
96+
# If you wish to build the manager image targeting other platforms you can use the --platform flag.
97+
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
98+
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
99+
.PHONY: docker-build
100+
docker-build: ## Build docker image with the manager.
101+
$(CONTAINER_TOOL) build -t ${IMG} .
102+
103+
.PHONY: docker-push
104+
docker-push: ## Push docker image with the manager.
105+
$(CONTAINER_TOOL) push ${IMG}
106+
107+
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
108+
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
109+
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
110+
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
111+
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
112+
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
113+
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
114+
.PHONY: docker-buildx
115+
docker-buildx: ## Build and push docker image for the manager for cross-platform support
116+
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
117+
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
118+
- $(CONTAINER_TOOL) buildx create --name project-v3-builder
119+
$(CONTAINER_TOOL) buildx use project-v3-builder
120+
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
121+
- $(CONTAINER_TOOL) buildx rm project-v3-builder
122+
rm Dockerfile.cross

0 commit comments

Comments
 (0)