Skip to content

Commit 6ec6dbb

Browse files
committed
Init
1 parent ab3c5da commit 6ec6dbb

File tree

13 files changed

+946
-1
lines changed

13 files changed

+946
-1
lines changed

.github/workflows/docker.yaml

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: Docker
2+
run-name: "${{ inputs.releaseVersion }}"
3+
4+
on:
5+
pull_request:
6+
branches:
7+
- main
8+
push:
9+
branches:
10+
- main
11+
workflow_dispatch:
12+
inputs:
13+
releaseVersion:
14+
type: string
15+
description: Version of the image to push
16+
required: true
17+
18+
permissions:
19+
contents: write
20+
packages: write
21+
checks: write
22+
statuses: write
23+
24+
jobs:
25+
build:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Check out repository
29+
uses: actions/checkout@v4
30+
31+
- name: Set tag name
32+
run: |
33+
echo "TAG_NAME=dev" >> $GITHUB_ENV
34+
35+
- name: Set release tag name
36+
if: github.event_name == 'workflow_dispatch'
37+
run: |
38+
TAG_NAME=${{ github.event.inputs.releaseVersion }}
39+
echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV
40+
41+
- name: Docker meta
42+
id: meta
43+
uses: docker/metadata-action@v5
44+
with:
45+
images: ghcr.io/${{ github.repository }}
46+
tags: |
47+
type=raw,value=latest
48+
type=raw,value=${{ env.TAG_NAME }}
49+
50+
- name: Set up Docker Buildx
51+
uses: docker/setup-buildx-action@v3
52+
53+
- name: Login to GitHub Container Registry
54+
uses: docker/login-action@v3
55+
if: github.event_name == 'workflow_dispatch'
56+
with:
57+
registry: ghcr.io
58+
username: ${{ github.actor }}
59+
password: ${{ secrets.GITHUB_TOKEN }}
60+
61+
- name: Build and push by digest
62+
id: build
63+
uses: docker/build-push-action@v5
64+
with:
65+
context: .
66+
platforms: linux/amd64,linux/arm64
67+
build-args: |
68+
VERSION=${{ env.TAG_NAME }}
69+
labels: ${{ steps.meta.outputs.labels }}
70+
cache-from: type=gha
71+
cache-to: type=gha,mode=max
72+
outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=${{ github.event_name == 'workflow_dispatch' }}
73+
74+
- name: Export digest
75+
if: github.event_name == 'workflow_dispatch'
76+
run: |
77+
mkdir -p /tmp/digests
78+
digest="${{ steps.build.outputs.digest }}"
79+
touch "/tmp/digests/${digest#sha256:}"
80+
81+
- name: Create manifest list and push
82+
if: github.event_name == 'workflow_dispatch'
83+
working-directory: /tmp/digests
84+
run: |
85+
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
86+
$(printf 'ghcr.io/${{ github.repository }}@sha256:%s ' *)
87+
88+
- name: Inspect image
89+
if: github.event_name == 'workflow_dispatch'
90+
run: |
91+
docker buildx imagetools inspect ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
92+
93+
- name: Create Release
94+
id: create_release
95+
uses: ncipollo/release-action@v1
96+
if: github.event_name == 'workflow_dispatch'
97+
with:
98+
name: ${{ github.event.inputs.releaseVersion }}
99+
generateReleaseNotes: true
100+
commit: ${{ github.sha }}
101+
tag: ${{ github.event.inputs.releaseVersion }}
102+
makeLatest: true
103+
env:
104+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Dockerfile

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
ARG VERSION
2+
3+
FROM golang:1.22 AS build
4+
5+
COPY . /src
6+
RUN cd /src && go build -ldflags="-X 'github.com/plumber-cd/argocd-applicationset-namespaces-generator-plugin/cmd/version.Version=$VERSION'" -o /bin/argocd-applicationset-namespaces-generator-plugin
7+
8+
FROM ubuntu:latest
9+
10+
RUN useradd -s /bin/bash -u 999 argocd
11+
WORKDIR /home/argocd
12+
USER argocd
13+
14+
ENTRYPOINT ["/usr/local/bin/argocd-applicationset-namespaces-generator-plugin", "server"]

README.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
11
# argocd-applicationset-namespaces-generator-plugin
2-
Namespaces Generator that discovers namespaces in a target cluster
2+
3+
Namespaces Generator that discovers namespaces in a target cluster.
4+
5+
**THIS IS NOT FINISHED**
6+
7+
# Testing
8+
9+
```bash
10+
go run ./... -v=0 --log-format=text server --local
11+
curl -X POST -H "Content-Type: application/json" -d @testdata/request.json http://localhost:8080/api/v1/getparams.execute
12+
```

cmd/cmd.go

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"log/slog"
7+
"os"
8+
"os/exec"
9+
"strings"
10+
11+
"github.com/spf13/cobra"
12+
"github.com/spf13/viper"
13+
14+
versionCmd "github.com/plumber-cd/argocd-applicationset-namespaces-generator-plugin/cmd/version"
15+
16+
serverCmd "github.com/plumber-cd/argocd-applicationset-namespaces-generator-plugin/cmd/server"
17+
)
18+
19+
var rootCmd = &cobra.Command{
20+
Use: "argocd-applicationset-namespaces-generator-plugin",
21+
RunE: func(cmd *cobra.Command, args []string) error {
22+
return fmt.Errorf("no command specified")
23+
},
24+
}
25+
26+
func Exec() {
27+
if err := rootCmd.Execute(); err != nil {
28+
exitErr, ok := err.(*exec.ExitError)
29+
if ok {
30+
os.Exit(exitErr.ExitCode())
31+
}
32+
33+
fmt.Fprintln(os.Stderr, err)
34+
os.Exit(1)
35+
}
36+
}
37+
38+
func init() {
39+
cobra.OnInitialize(initConfig)
40+
41+
rootCmd.PersistentFlags().IntP("verbosity", "v", 0, "Set verbosity level")
42+
rootCmd.PersistentFlags().String("log-format", "json", "Set log output (json, text)")
43+
44+
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
45+
log.Panic(err)
46+
}
47+
48+
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
49+
if err := viper.BindPFlags(cmd.Flags()); err != nil {
50+
log.Panic(err)
51+
}
52+
}
53+
54+
rootCmd.AddCommand(versionCmd.Cmd)
55+
rootCmd.AddCommand(serverCmd.Cmd)
56+
}
57+
58+
func initConfig() {
59+
viper.AutomaticEnv()
60+
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
61+
viper.SetEnvPrefix("ARGOCD_APPLICATIONSET_NAMESPACES_PLUGIN")
62+
63+
format := viper.GetString("log-format")
64+
level := slog.Level(-viper.GetInt("verbosity"))
65+
66+
handlerOptions := &slog.HandlerOptions{
67+
Level: level,
68+
AddSource: level <= slog.LevelDebug,
69+
}
70+
var handler slog.Handler
71+
switch format {
72+
case "json":
73+
handler = slog.NewJSONHandler(os.Stderr, handlerOptions)
74+
case "text":
75+
suppress := func(
76+
next func([]string, slog.Attr) slog.Attr,
77+
) func([]string, slog.Attr) slog.Attr {
78+
return func(groups []string, a slog.Attr) slog.Attr {
79+
if a.Key == slog.TimeKey {
80+
return slog.Attr{}
81+
}
82+
if next == nil {
83+
return a
84+
}
85+
return next(groups, a)
86+
}
87+
}
88+
handlerOptions.ReplaceAttr = suppress(handlerOptions.ReplaceAttr)
89+
handler = slog.NewTextHandler(os.Stdout, handlerOptions)
90+
default:
91+
log.Panicf("unknown log format: %s", format)
92+
}
93+
94+
slog.SetDefault(slog.New(handler))
95+
}

cmd/server/http.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package server
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"log/slog"
7+
"net/http"
8+
"strings"
9+
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
)
12+
13+
type RequestParameters struct {
14+
ClusterEndpoint *string `json:"clusterEndpoint,omitempty"`
15+
UseLocalCA *bool `json:"useLocalCA,omitempty"`
16+
LabelSelector map[string]string `json:"labelSelector,omitempty"`
17+
}
18+
19+
type RequestInput struct {
20+
Parameters *RequestParameters `json:"parameters,omitempty"`
21+
}
22+
23+
type ResponseParameters struct {
24+
Namespace *string `json:"namespace,omitempty"`
25+
}
26+
27+
type ResponseOutput struct {
28+
Parameters []*ResponseParameters `json:"parameters,omitempty"`
29+
}
30+
31+
type ResponseBody struct {
32+
Output *ResponseOutput `json:"output,omitempty"`
33+
}
34+
35+
func (c *ServerConfig) secretsHandler(ctx context.Context) func(http.ResponseWriter, *http.Request) {
36+
return func(w http.ResponseWriter, r *http.Request) {
37+
slog.Debug("Received request", "address", r.RemoteAddr, "url", r.URL)
38+
if r.Method != http.MethodPost {
39+
slog.Debug("Method not allowed", "method", r.Method, "address", r.RemoteAddr, "url", r.URL)
40+
w.WriteHeader(http.StatusMethodNotAllowed)
41+
_, _ = w.Write([]byte("Method not allowed"))
42+
return
43+
}
44+
if r.Header.Get("Content-Type") != "application/json" {
45+
slog.Debug("Unsupported media type", "media-type", r.Header.Get("Content-Type"), "address", r.RemoteAddr, "url", r.URL)
46+
w.WriteHeader(http.StatusUnsupportedMediaType)
47+
_, _ = w.Write([]byte("Unsupported media type"))
48+
return
49+
}
50+
if c.ListenToken != "" && r.Header.Get("Authorization") != "Bearer "+c.ListenToken {
51+
slog.Debug("Unauthorized", "address", r.RemoteAddr, "url", r.URL)
52+
w.WriteHeader(http.StatusUnauthorized)
53+
_, _ = w.Write([]byte("Unauthorized"))
54+
return
55+
}
56+
57+
input := RequestInput{}
58+
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
59+
slog.Debug("Unable to read input json", "error", err, "address", r.RemoteAddr, "url", r.URL)
60+
w.WriteHeader(http.StatusBadRequest)
61+
_, _ = w.Write([]byte("Bad request"))
62+
return
63+
}
64+
65+
if input.Parameters == nil {
66+
slog.Debug("No parameters provided", "address", r.RemoteAddr, "url", r.URL)
67+
w.WriteHeader(http.StatusBadRequest)
68+
_, _ = w.Write([]byte("Bad request"))
69+
return
70+
}
71+
72+
_, k8s, err := c.GetClient(input.Parameters)
73+
if err != nil {
74+
slog.Error("Failed to get k8s client", "error", err, "address", r.RemoteAddr, "url", r.URL)
75+
w.WriteHeader(http.StatusInternalServerError)
76+
_, _ = w.Write([]byte("Internal server error"))
77+
return
78+
}
79+
80+
listOptions := metav1.ListOptions{}
81+
82+
if input.Parameters != nil && input.Parameters.LabelSelector != nil {
83+
labels := []string{}
84+
for key, value := range input.Parameters.LabelSelector {
85+
labels = append(labels, key+"="+value)
86+
}
87+
listOptions.LabelSelector = strings.Join(labels, ",")
88+
slog.Debug("Using label selector", "labelSelector", listOptions.LabelSelector, "address", r.RemoteAddr, "url", r.URL)
89+
}
90+
91+
namespaces, err := k8s.CoreV1().Namespaces().List(ctx, listOptions)
92+
if err != nil {
93+
slog.Error("Failed to list namespaces", "error", err, "address", r.RemoteAddr, "url", r.URL)
94+
w.WriteHeader(http.StatusInternalServerError)
95+
_, _ = w.Write([]byte("Internal server error"))
96+
return
97+
}
98+
99+
output := ResponseBody{
100+
Output: &ResponseOutput{
101+
Parameters: []*ResponseParameters{},
102+
},
103+
}
104+
105+
for _, ns := range namespaces.Items {
106+
output.Output.Parameters = append(output.Output.Parameters, &ResponseParameters{
107+
Namespace: &ns.Name,
108+
})
109+
}
110+
111+
slog.Debug("Returning response", "address", r.RemoteAddr, "url", r.URL, "output", output)
112+
w.Header().Set("Content-Type", "application/json")
113+
if err := json.NewEncoder(w).Encode(output); err != nil {
114+
slog.Error("Failed to encode response", "error", err, "address", r.RemoteAddr, "url", r.URL)
115+
w.WriteHeader(http.StatusInternalServerError)
116+
_, _ = w.Write([]byte("Internal server error"))
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)