-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f631085
Showing
25 changed files
with
1,882 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/bin | ||
/deploy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/bin | ||
/test/values-test.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
FROM golang:alpine as builder | ||
|
||
WORKDIR /go/src/github.com/ETSGlobal/translations-refresher | ||
|
||
COPY go.mod . | ||
COPY go.sum . | ||
|
||
RUN go mod download | ||
|
||
COPY . . | ||
|
||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o bin/refresher *.go | ||
|
||
|
||
FROM alpine:latest | ||
|
||
RUN apk --no-cache add ca-certificates | ||
|
||
COPY --from=builder /go/src/github.com/ETSGlobal/translations-refresher/bin/refresher /usr/local/bin | ||
|
||
ENTRYPOINT ["refresher"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
translations-refresher | ||
====================== | ||
|
||
Automatic rollout kubernetes deployments on Loco translations updates. | ||
|
||
## Usage | ||
|
||
Run the app locally | ||
```bash | ||
go run *.go -kubeconfig ~/.kube/config | ||
``` | ||
|
||
Build docker image | ||
```bash | ||
docker build -t translations-refresher:test . | ||
``` | ||
|
||
Install Helm chart | ||
```bash | ||
helm install translations-refresher deploy/helm | ||
``` | ||
|
||
## Testing | ||
|
||
1. Install Kind (Kubernetes IN Docker) https://kind.sigs.k8s.io/docs/user/quick-start/#installation | ||
|
||
2. Launch the local cluster | ||
```bash | ||
kind create cluster --name translations --config test/cluster.yaml | ||
``` | ||
|
||
3. Load the docker image inside the cluster | ||
```bash | ||
kind load docker-image translations-refresher:test --name translations | ||
``` | ||
|
||
4. Install the chart | ||
```bash | ||
helm install translations-refresher deploy/helm \ | ||
--set image.tag=test \ | ||
--set env.LOCO_API_KEY_DOCUMENTS=<loco_api_key> \ | ||
--set env.LOCO_API_KEY_CATALOG=<loco_api_key> \ | ||
--set env.LOCO_API_KEY_EMAILS=<loco_api_key> | ||
``` | ||
|
||
5. Install the test deployment | ||
kubectl apply -f test/deployment.yaml | ||
|
||
The refresher is now running in the cluster and should have mutated the test deployment. | ||
```bash | ||
kubectl describe deployment/test-app | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"sync" | ||
"time" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/promhttp" | ||
"github.com/slok/kubewebhook/pkg/observability/metrics" | ||
"k8s.io/client-go/kubernetes" | ||
"k8s.io/client-go/rest" | ||
"k8s.io/client-go/tools/clientcmd" | ||
|
||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" | ||
) | ||
|
||
// Config holds the application configuration | ||
type Config struct { | ||
enableCron bool | ||
enableWebhook bool | ||
kubeconfig string | ||
locoAPIKeys map[string]string | ||
tlsCertFile string | ||
tlsPrivateKeyFile string | ||
cronPeriod time.Duration | ||
} | ||
|
||
// App represents the main application object | ||
type App struct { | ||
config *Config | ||
translations TranslationsProvider | ||
refresher *Refresher | ||
} | ||
|
||
func newApp(c *Config) *App { | ||
return &App{config: c} | ||
} | ||
|
||
// Init initializes the application | ||
func (app *App) Init() error { | ||
// create the kubernetes client config | ||
config, err := app.configFromKubeConfig() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// create the clientset | ||
clientset, err := kubernetes.NewForConfig(config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// create the Refresher service | ||
app.refresher = NewRefresher(clientset) | ||
|
||
// create the Loco clients | ||
locoClients := CreateLocoClients(app.config.locoAPIKeys) | ||
|
||
// create the Loco translations provider | ||
app.translations = NewLocoProvider(locoClients) | ||
|
||
return nil | ||
} | ||
|
||
// Run the application | ||
func (app *App) Run() { | ||
// create Prometheus recorder | ||
recorder := metrics.NewPrometheus(prometheus.DefaultRegisterer) | ||
|
||
// compute translation hashes | ||
hashes := app.translations.Fetch() | ||
|
||
if app.config.enableCron { | ||
cron := NewCron(app.config.cronPeriod) | ||
go cron.Run(func() { | ||
// make sure we have the latest translations | ||
app.translations.Fetch() | ||
// update kubernetes deployments | ||
app.refresher.Refresh(hashes) | ||
}) | ||
} | ||
|
||
if app.config.enableWebhook { | ||
// We need the handler func with all the translations sync logic | ||
deployHandler := Appsv1ResourceHandler("etsglobal.org", hashes) | ||
|
||
// Create the mutating webhook | ||
mw := NewMutatingWebhook(deployHandler, recorder) | ||
// Start the mutating webhook server in a separate goroutine | ||
go func() { | ||
err := mw.ListenAndServeTLS(":8443", app.config.tlsCertFile, app.config.tlsPrivateKeyFile) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Error serving webhook: %s", err) | ||
os.Exit(1) | ||
} | ||
}() | ||
} | ||
|
||
var wg sync.WaitGroup | ||
|
||
go func() { | ||
wg.Add(1) | ||
defer wg.Done() | ||
|
||
// HTTP server | ||
mux := http.NewServeMux() | ||
mux.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(200) | ||
})) | ||
mux.Handle("/metrics", http.Handler(promhttp.Handler())) | ||
|
||
fmt.Println("Listening on port 8080") | ||
err := http.ListenAndServe(":8080", mux) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Error serving HTTP requests: %s", err) | ||
} | ||
}() | ||
|
||
// When everythins is up and running, we can finally run the refresh | ||
app.refresher.Refresh(hashes) | ||
|
||
wg.Wait() | ||
} | ||
|
||
func (app App) configFromKubeConfig() (*rest.Config, error) { | ||
_, err := os.Stat(app.config.kubeconfig) | ||
if os.IsNotExist(err) { | ||
// kubeconfig file doesn't exist, try in-cluster config | ||
return rest.InClusterConfig() | ||
} | ||
|
||
// use the current context in kubeconfig | ||
return clientcmd.BuildConfigFromFlags("", app.config.kubeconfig) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package main | ||
|
||
import "time" | ||
|
||
// Cron represents a sceduled cron task | ||
type Cron struct { | ||
period time.Duration | ||
} | ||
|
||
// NewCron creates a new Cron instance | ||
func NewCron(p time.Duration) *Cron { | ||
return &Cron{p} | ||
} | ||
|
||
// Run starts the Cron | ||
func (c Cron) Run(f func()) { | ||
ticker := time.NewTicker(c.period) | ||
|
||
for { | ||
select { | ||
case <-ticker.C: | ||
f() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Patterns to ignore when building packages. | ||
# This supports shell glob matching, relative path matching, and | ||
# negation (prefixed with !). Only one pattern per line. | ||
.DS_Store | ||
# Common VCS dirs | ||
.git/ | ||
.gitignore | ||
.bzr/ | ||
.bzrignore | ||
.hg/ | ||
.hgignore | ||
.svn/ | ||
# Common backup files | ||
*.swp | ||
*.bak | ||
*.tmp | ||
*.orig | ||
*~ | ||
# Various IDEs | ||
.project | ||
.idea/ | ||
*.tmproj | ||
.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
apiVersion: v2 | ||
name: translations-refresher | ||
description: A Helm chart for Kubernetes | ||
type: application | ||
version: 0.1.0 | ||
appVersion: 0.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
1. Get the application URL by running these commands: | ||
{{- if contains "NodePort" .Values.service.type }} | ||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "translations-refresher.fullname" . }}) | ||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") | ||
echo http://$NODE_IP:$NODE_PORT | ||
{{- else if contains "LoadBalancer" .Values.service.type }} | ||
NOTE: It may take a few minutes for the LoadBalancer IP to be available. | ||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "translations-refresher.fullname" . }}' | ||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "translations-refresher.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") | ||
echo http://$SERVICE_IP:{{ .Values.service.port }} | ||
{{- else if contains "ClusterIP" .Values.service.type }} | ||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "translations-refresher.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") | ||
echo "Visit http://127.0.0.1:8080 to use your application" | ||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 | ||
{{- end }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
{{/* | ||
Expand the name of the chart. | ||
*/}} | ||
{{- define "translations-refresher.name" -}} | ||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} | ||
{{- end }} | ||
|
||
{{/* | ||
Create a default fully qualified app name. | ||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). | ||
If release name contains chart name it will be used as a full name. | ||
*/}} | ||
{{- define "translations-refresher.fullname" -}} | ||
{{- if .Values.fullnameOverride }} | ||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} | ||
{{- else }} | ||
{{- $name := default .Chart.Name .Values.nameOverride }} | ||
{{- if contains $name .Release.Name }} | ||
{{- .Release.Name | trunc 63 | trimSuffix "-" }} | ||
{{- else }} | ||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} | ||
{{- end }} | ||
{{- end }} | ||
{{- end }} | ||
|
||
{{/* | ||
Create chart name and version as used by the chart label. | ||
*/}} | ||
{{- define "translations-refresher.chart" -}} | ||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} | ||
{{- end }} | ||
|
||
{{/* | ||
Common labels | ||
*/}} | ||
{{- define "translations-refresher.labels" -}} | ||
helm.sh/chart: {{ include "translations-refresher.chart" . }} | ||
{{ include "translations-refresher.selectorLabels" . }} | ||
{{- if .Chart.AppVersion }} | ||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} | ||
{{- end }} | ||
app.kubernetes.io/managed-by: {{ .Release.Service }} | ||
{{- end }} | ||
|
||
{{/* | ||
Selector labels | ||
*/}} | ||
{{- define "translations-refresher.selectorLabels" -}} | ||
app.kubernetes.io/name: {{ include "translations-refresher.name" . }} | ||
app.kubernetes.io/instance: {{ .Release.Name }} | ||
{{- end }} | ||
|
||
{{/* | ||
Create the name of the service account to use | ||
*/}} | ||
{{- define "translations-refresher.serviceAccountName" -}} | ||
{{- if .Values.serviceAccount.create }} | ||
{{- default (include "translations-refresher.fullname" .) .Values.serviceAccount.name }} | ||
{{- else }} | ||
{{- default "default" .Values.serviceAccount.name }} | ||
{{- end }} | ||
{{- end }} |
Oops, something went wrong.