diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d399e02..1090efc 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -2,7 +2,7 @@ # yaml-language-server: $schema=https://goreleaser.com/static/schema.json # vim: set ts=2 sw=2 tw=0 fo=cnqoj -version: 1 +version: 2 before: hooks: @@ -32,6 +32,41 @@ archives: - goos: windows format: zip +nfpms: + - homepage: https://github.com/dadav/gorge + maintainer: dadav + description: |- + Gorge is a puppet forge implementation in go. + license: Apache 2.0 + formats: + - apk + - deb + - rpm + - termux.deb + - archlinux + provides: + - gorge + contents: + - src: gorge.service + dst: /usr/lib/systemd/system/gorge.service + - src: defaults.yaml + dst: /etc/gorge.yaml + type: "config|noreplace" + +dockers: + - goos: linux + goarch: amd64 + image_templates: + - 'ghcr.io/dadav/gorge:{{ .Tag }}' + - 'ghcr.io/dadav/gorge:latest' + build_flag_templates: + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.authors=dadav" + - "--label=org.opencontainers.image.url=https://github.com/dadav/gorge" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + changelog: sort: asc filters: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d36c85f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM scratch +ENTRYPOINT ["/gorge"] +COPY gorge / diff --git a/README.md b/README.md index a9926a0..7680890 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ This project is still in an very early stage. Contributions are very welcome. You put your modules in the directory `~/.gorge/modules/$module/$release.tar.gz` and gorge will send them to incoming requests from puppet or r10k. -If the module is not found locally it will forward to request (if configured) to an upstream +If the module is not found locally it will forward the request (if configured) to an upstream forge. -The result of this upstream request will be cached for one day (if not disabled with `--no-cache`). +The results will be cached for one day (if not disabled with `--no-cache`). Usually the request results in a module tarball being downloaded. You can set `--import-proxied-releases` to automatically import them in your `~/.gorge/modules` directory. diff --git a/cmd/serve.go b/cmd/serve.go index cb3cf77..adb2a35 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -179,6 +179,18 @@ You can also enable the caching functionality to speed things up.`, r.Mount("/", apiRouter) + r.Get("/readyz", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + w.Write([]byte(`{"message": "ok"}`)) + }) + + r.Get("/livez", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + w.Write([]byte(`{"message": "ok"}`)) + }) + bindPort := fmt.Sprintf("%s:%d", config.Bind, config.Port) log.Log.Infof("Listen on %s", bindPort) diff --git a/defaults.yaml b/defaults.yaml new file mode 100644 index 0000000..78afc06 --- /dev/null +++ b/defaults.yaml @@ -0,0 +1,35 @@ +--- +# The forge api version to use. Currently only v3 is supported. +api-version: v3 +# The backend type to use. Currently only filesystem is supported. +backend: filesystem +# Max seconds to keep the cached responses. +cache-max-age: 86400 +# The host to bind the webservice to. +bind: 127.0.0.1 +# The prefixes of requests to cache responses from. Multiple entries must be separated by comma. +cache-prefixes: /v3/files +# Value of the `Access-Control-Allow-Origin` header. +cors: "*" +# Enables the dev mode. +dev: false +# List of comma separated upstream forge(s) to use when local requests return 404 +fallback-proxy: +# Import proxied modules into local backend. +import-proxied-releases: false +# Path to local modules. +modulesdir: ~/.gorge/modules +# Seconds between scans of directory containing all the modules +modules-scan-sec: 0 +# Disable cache functionality. +no-cache: false +# Port to bind the webservice to. +port: 8080 +# The jwt secret used in the protected endpoint validation +jwt-secret: changeme +# The path to write the jwt token to +jwt-token-path: ~/.gorge/token +# Path to tls cert file +tls-cert: "" +# Path to tls key file +tls-key: "" diff --git a/gorge.service b/gorge.service new file mode 100644 index 0000000..78f3ca5 --- /dev/null +++ b/gorge.service @@ -0,0 +1,25 @@ +[Unit] +Description=Gorge is a puppet forge server written in Go + +[Service] +Type=simple +ExecStart=/usr/bin/gorge --config /etc/gorge.yaml serve +Restart=on-failure +NoNewPrivileges=yes +PrivateTmp=yes +DevicePolicy=closed +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +MemoryDenyWriteExecute=yes +LockPersonality=yes +ProtectClock=yes +ProtectHostname=yes +PrivateUsers=yes + +[Install] +WantedBy=multi-user.target diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 0000000..b606ef4 --- /dev/null +++ b/helm/README.md @@ -0,0 +1,5 @@ +# ⚡ Helm + +This directory contains the gorge helm chart. + +You can use this to easily install gorge in kubernetes. diff --git a/helm/gorge/.helmignore b/helm/gorge/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/gorge/.helmignore @@ -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/ diff --git a/helm/gorge/.pre-commit-config.yaml b/helm/gorge/.pre-commit-config.yaml new file mode 100644 index 0000000..7764613 --- /dev/null +++ b/helm/gorge/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +--- +repos: + - repo: https://github.com/dadav/helm-schema + rev: 0.11.4 + hooks: + - id: helm-schema + # for all available options: helm-schema -h + args: + # directory to search recursively within for charts + - --chart-search-root=. + + # don't analyze dependencies + - --no-dependencies + + # add references to values file if not exist + - --add-schema-reference + + # list of fields to skip from being created by default + # e.g. generate a relatively permissive schema + # - "--skip-auto-generation=required,additionalProperties" diff --git a/helm/gorge/Chart.yaml b/helm/gorge/Chart.yaml new file mode 100644 index 0000000..7a9dbe5 --- /dev/null +++ b/helm/gorge/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: gorge +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: 0.4.1-alpha diff --git a/helm/gorge/templates/deployment.yaml b/helm/gorge/templates/deployment.yaml new file mode 100644 index 0000000..a0778f6 --- /dev/null +++ b/helm/gorge/templates/deployment.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gorge +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: gorge + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + app: gorge + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ .Values.serviceAccount.name }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: GORGE_MODULESDIR + value: /modules + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /livez + port: http + readinessProbe: + httpGet: + path: /readyz + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - mountPath: /modules + name: modules + volumes: + - name: modules + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: gorge + {{- else }} + emptyDir: {} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/gorge/templates/ingress.yaml b/helm/gorge/templates/ingress.yaml new file mode 100644 index 0000000..b52e042 --- /dev/null +++ b/helm/gorge/templates/ingress.yaml @@ -0,0 +1,26 @@ +{{- if (and (.Values.ingress.enabled) (not .Values.openshift)) }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gorge + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: gorge + port: + name: http +{{- end }} diff --git a/helm/gorge/templates/pvc.yaml b/helm/gorge/templates/pvc.yaml new file mode 100644 index 0000000..e47e8af --- /dev/null +++ b/helm/gorge/templates/pvc.yaml @@ -0,0 +1,16 @@ +{{- if .Values.persistence.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: gorge +spec: + {{- with .Values.persistence.storageClass }} + storageClassName: {{ . }} + {{- end }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.persitence.size }} +{{- end }} diff --git a/helm/gorge/templates/route.yaml b/helm/gorge/templates/route.yaml new file mode 100644 index 0000000..6801e70 --- /dev/null +++ b/helm/gorge/templates/route.yaml @@ -0,0 +1,18 @@ +{{- if (and (.Values.ingress.enabled) (.Values.openshift)) }} +--- +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: gorge + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + host: {{ .Values.ingress.host }} + port: + targetPort: http + to: + kind: Service + name: gorge +{{- end }} diff --git a/helm/gorge/templates/service.yaml b/helm/gorge/templates/service.yaml new file mode 100644 index 0000000..75ff0c4 --- /dev/null +++ b/helm/gorge/templates/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: gorge +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: http + protocol: TCP + name: http + selector: + app: gorge diff --git a/helm/gorge/templates/serviceaccount.yaml b/helm/gorge/templates/serviceaccount.yaml new file mode 100644 index 0000000..56cf5d6 --- /dev/null +++ b/helm/gorge/templates/serviceaccount.yaml @@ -0,0 +1,11 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.serviceAccount.name }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/helm/gorge/values.schema.json b/helm/gorge/values.schema.json new file mode 100644 index 0000000..eb13f9a --- /dev/null +++ b/helm/gorge/values.schema.json @@ -0,0 +1,302 @@ +{ + "additionalProperties": false, + "properties": { + "affinity": { + "additionalProperties": false, + "description": "Configure node affinity", + "title": "affinity", + "type": "object" + }, + "global": { + "description": "Global values are values that can be accessed from any chart or subchart by exactly the same name.", + "title": "global", + "type": "object" + }, + "image": { + "additionalProperties": false, + "properties": { + "pullPolicy": { + "default": "IfNotPresent", + "description": "Sets the pull policy.", + "title": "pullPolicy", + "type": "string" + }, + "registry": { + "default": "ghcr.io", + "description": "Registry to use.", + "title": "registry", + "type": "string" + }, + "repository": { + "default": "dadav/gorge", + "description": "Repo to use.", + "title": "repository", + "type": "string" + }, + "tag": { + "default": "", + "description": "Image tag to use. If unset, latest will be used.", + "title": "tag", + "type": "string" + } + }, + "description": "Image related options", + "title": "image", + "type": "object", + "required": [ + "repository" + ] + }, + "imagePullSecrets": { + "items": { + "properties": { + "password": { + "type": "string" + }, + "url": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url", + "username", + "password" + ] + }, + "description": "Optional pullsecrets required to get the gorge image.", + "title": "imagePullSecrets", + "type": "array" + }, + "ingress": { + "additionalProperties": false, + "properties": { + "annotations": { + "additionalProperties": false, + "description": "Ingress/route annotations", + "title": "annotations", + "type": "object" + }, + "className": { + "default": "", + "description": "Ingress className, will be ignored if openshift=true", + "title": "className", + "type": "string" + }, + "enabled": { + "default": false, + "description": "On/Off toggle", + "title": "enabled", + "type": "boolean" + }, + "host": { + "default": "foo.example.com", + "description": "Hostname", + "title": "host", + "type": "string" + } + }, + "description": "Ingress / Route related options", + "title": "ingress", + "type": "object", + "required": [ + "enabled", + "className", + "annotations", + "host" + ] + }, + "nodeSelector": { + "additionalProperties": false, + "description": "Select deployment nodes", + "title": "nodeSelector", + "type": "object" + }, + "openshift": { + "default": false, + "description": "Enables openshift mode (routes)", + "title": "openshift", + "type": "boolean" + }, + "persistence": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": true, + "description": "Toggle persistency on / off", + "title": "enabled", + "type": "boolean" + }, + "size": { + "default": "1Gi", + "description": "The size to request in the pvc", + "title": "size", + "type": "string" + }, + "storageClass": { + "default": "", + "description": "The storageClass to use in the pvc", + "title": "storageClass", + "type": "string" + } + }, + "description": "Configure persistency", + "title": "persistence", + "type": "object", + "required": [ + "enabled", + "storageClass", + "size" + ] + }, + "podAnnotations": { + "additionalProperties": false, + "description": "Annotations used in the pods", + "title": "podAnnotations", + "type": "object" + }, + "replicaCount": { + "default": 1, + "description": "yaml-language-server: $schema=values.schema.json\nNumber of pods", + "title": "replicaCount", + "type": "integer" + }, + "resources": { + "additionalProperties": false, + "properties": { + "limits": { + "additionalProperties": false, + "properties": { + "cpu": { + "default": "", + "description": "Maximum cpu", + "title": "cpu", + "oneOf": [ + { + "type": "integer" + }, + { + "pattern": "^\\d+m$", + "type": "string" + }, + { + "type": "null" + } + ] + }, + "memory": { + "default": "128Mi", + "pattern": "^\\d+[MTPG]i$", + "description": "Maximum memory", + "title": "memory", + "type": "string" + } + }, + "title": "limits", + "type": "object" + }, + "requests": { + "additionalProperties": false, + "properties": { + "cpu": { + "default": "100m", + "description": "Minimum required cpu", + "title": "cpu", + "oneOf": [ + { + "type": "integer" + }, + { + "pattern": "^\\d+m$", + "type": "string" + } + ] + }, + "memory": { + "default": "128Mi", + "pattern": "^\\d+[MTPG]i$", + "description": "Minimum required memory", + "title": "memory", + "type": "string" + } + }, + "title": "requests", + "type": "object" + } + }, + "title": "resources", + "type": "object", + "required": [ + "requests", + "limits" + ] + }, + "securityContext": { + "additionalProperties": false, + "description": "Container securityContexts", + "title": "securityContext", + "type": "object" + }, + "serviceAccount": { + "additionalProperties": false, + "properties": { + "annotations": { + "additionalProperties": false, + "description": "Annotations to add to the service account", + "title": "annotations", + "type": "object" + }, + "automount": { + "default": true, + "description": "Automatically mount a ServiceAccount's API credentials?", + "title": "automount", + "type": "boolean" + }, + "create": { + "default": true, + "description": "Specifies whether a service account should be created", + "title": "create", + "type": "boolean" + }, + "name": { + "default": "gorge", + "description": "The name of the service account to use.", + "title": "name", + "type": "string" + } + }, + "description": "ServiceAccount related options", + "title": "serviceAccount", + "type": "object", + "required": [ + "create", + "automount", + "annotations", + "name" + ] + }, + "tolerations": { + "items": {}, + "description": "Add tolerations for nodes", + "title": "tolerations", + "type": "array" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "persistence", + "image", + "serviceAccount", + "podAnnotations", + "securityContext", + "ingress", + "resources", + "nodeSelector", + "tolerations", + "affinity" + ] +} \ No newline at end of file diff --git a/helm/gorge/values.yaml b/helm/gorge/values.yaml new file mode 100644 index 0000000..d84180a --- /dev/null +++ b/helm/gorge/values.yaml @@ -0,0 +1,141 @@ +# yaml-language-server: $schema=values.schema.json +--- +# @schema +# type: "integer" +# @schema +# -- Number of pods +replicaCount: 1 + +# @schema +# type: "boolean" +# @schema +# -- Enables openshift mode (routes) +openshift: false + +# -- Configure persistency +persistence: + # -- Toggle persistency on / off + enabled: true + # -- The storageClass to use in the pvc + storageClass: "" + # -- The size to request in the pvc + size: 1Gi + +# -- Image related options +image: + # @schema + # type: string + # @schema + # -- Registry to use. + registry: ghcr.io + # @schema + # type: string + # required: true + # @schema + # -- Repo to use. + repository: dadav/gorge + # @schema + # type: string + # @schema + # -- Sets the pull policy. + pullPolicy: IfNotPresent + # @schema + # type: string + # @schema + # -- Image tag to use. If unset, latest will be used. + tag: "" + +# @schema +# type: array +# items: +# type: object +# properties: +# url: +# type: string +# required: true +# username: +# type: string +# required: true +# password: +# type: string +# required: true +# @schema +# -- Optional pullsecrets required to get the gorge image. +imagePullSecrets: [] + +# -- ServiceAccount related options +serviceAccount: + # -- Specifies whether a service account should be created + create: true + # -- Automatically mount a ServiceAccount's API credentials? + automount: true + # -- Annotations to add to the service account + annotations: {} + # -- The name of the service account to use. + name: "gorge" + +# -- Annotations used in the pods +podAnnotations: {} + +# -- Container securityContexts +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# -- Ingress / Route related options +ingress: + # -- On/Off toggle + enabled: false + # -- Ingress className, will be ignored if openshift=true + className: "" + # -- Ingress/route annotations + annotations: {} + # -- Hostname + host: foo.example.com + + +resources: + requests: + # @schema + # oneOf: + # - type: "integer" + # - type: "string" + # pattern: ^\d+m$ + # @schema + # -- Minimum required cpu + cpu: 100m + # @schema + # type: "string" + # pattern: ^\d+[MTPG]i$ + # @schema + # -- Minimum required memory + memory: 128Mi + limits: + # @schema + # oneOf: + # - type: "integer" + # - type: "string" + # pattern: ^\d+m$ + # - type: "null" + # @schema + # -- Maximum cpu + cpu: + # @schema + # type: "string" + # pattern: ^\d+[MTPG]i$ + # @schema + # -- Maximum memory + memory: 128Mi + +# -- Select deployment nodes +nodeSelector: {} + +# -- Add tolerations for nodes +tolerations: [] + +# -- Configure node affinity +affinity: {}