Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
dee-kryvenko committed Mar 15, 2024
1 parent ab3c5da commit 6ec6dbb
Show file tree
Hide file tree
Showing 13 changed files with 946 additions and 1 deletion.
104 changes: 104 additions & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Docker
run-name: "${{ inputs.releaseVersion }}"

on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:
inputs:
releaseVersion:
type: string
description: Version of the image to push
required: true

permissions:
contents: write
packages: write
checks: write
statuses: write

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set tag name
run: |
echo "TAG_NAME=dev" >> $GITHUB_ENV
- name: Set release tag name
if: github.event_name == 'workflow_dispatch'
run: |
TAG_NAME=${{ github.event.inputs.releaseVersion }}
echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=latest
type=raw,value=${{ env.TAG_NAME }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
if: github.event_name == 'workflow_dispatch'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
build-args: |
VERSION=${{ env.TAG_NAME }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=${{ github.event_name == 'workflow_dispatch' }}

- name: Export digest
if: github.event_name == 'workflow_dispatch'
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Create manifest list and push
if: github.event_name == 'workflow_dispatch'
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/${{ github.repository }}@sha256:%s ' *)
- name: Inspect image
if: github.event_name == 'workflow_dispatch'
run: |
docker buildx imagetools inspect ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
- name: Create Release
id: create_release
uses: ncipollo/release-action@v1
if: github.event_name == 'workflow_dispatch'
with:
name: ${{ github.event.inputs.releaseVersion }}
generateReleaseNotes: true
commit: ${{ github.sha }}
tag: ${{ github.event.inputs.releaseVersion }}
makeLatest: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ARG VERSION

FROM golang:1.22 AS build

COPY . /src
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

FROM ubuntu:latest

RUN useradd -s /bin/bash -u 999 argocd
WORKDIR /home/argocd
USER argocd

ENTRYPOINT ["/usr/local/bin/argocd-applicationset-namespaces-generator-plugin", "server"]
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
# argocd-applicationset-namespaces-generator-plugin
Namespaces Generator that discovers namespaces in a target cluster

Namespaces Generator that discovers namespaces in a target cluster.

**THIS IS NOT FINISHED**

# Testing

```bash
go run ./... -v=0 --log-format=text server --local
curl -X POST -H "Content-Type: application/json" -d @testdata/request.json http://localhost:8080/api/v1/getparams.execute
```
95 changes: 95 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"fmt"
"log"
"log/slog"
"os"
"os/exec"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"

versionCmd "github.com/plumber-cd/argocd-applicationset-namespaces-generator-plugin/cmd/version"

serverCmd "github.com/plumber-cd/argocd-applicationset-namespaces-generator-plugin/cmd/server"
)

var rootCmd = &cobra.Command{
Use: "argocd-applicationset-namespaces-generator-plugin",
RunE: func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("no command specified")
},
}

func Exec() {
if err := rootCmd.Execute(); err != nil {
exitErr, ok := err.(*exec.ExitError)
if ok {
os.Exit(exitErr.ExitCode())
}

fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func init() {
cobra.OnInitialize(initConfig)

rootCmd.PersistentFlags().IntP("verbosity", "v", 0, "Set verbosity level")
rootCmd.PersistentFlags().String("log-format", "json", "Set log output (json, text)")

if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
log.Panic(err)
}

rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
log.Panic(err)
}
}

rootCmd.AddCommand(versionCmd.Cmd)
rootCmd.AddCommand(serverCmd.Cmd)
}

func initConfig() {
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
viper.SetEnvPrefix("ARGOCD_APPLICATIONSET_NAMESPACES_PLUGIN")

format := viper.GetString("log-format")
level := slog.Level(-viper.GetInt("verbosity"))

handlerOptions := &slog.HandlerOptions{
Level: level,
AddSource: level <= slog.LevelDebug,
}
var handler slog.Handler
switch format {
case "json":
handler = slog.NewJSONHandler(os.Stderr, handlerOptions)
case "text":
suppress := func(
next func([]string, slog.Attr) slog.Attr,
) func([]string, slog.Attr) slog.Attr {
return func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
if next == nil {
return a
}
return next(groups, a)
}
}
handlerOptions.ReplaceAttr = suppress(handlerOptions.ReplaceAttr)
handler = slog.NewTextHandler(os.Stdout, handlerOptions)
default:
log.Panicf("unknown log format: %s", format)
}

slog.SetDefault(slog.New(handler))
}
119 changes: 119 additions & 0 deletions cmd/server/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package server

import (
"context"
"encoding/json"
"log/slog"
"net/http"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type RequestParameters struct {
ClusterEndpoint *string `json:"clusterEndpoint,omitempty"`
UseLocalCA *bool `json:"useLocalCA,omitempty"`
LabelSelector map[string]string `json:"labelSelector,omitempty"`
}

type RequestInput struct {
Parameters *RequestParameters `json:"parameters,omitempty"`
}

type ResponseParameters struct {
Namespace *string `json:"namespace,omitempty"`
}

type ResponseOutput struct {
Parameters []*ResponseParameters `json:"parameters,omitempty"`
}

type ResponseBody struct {
Output *ResponseOutput `json:"output,omitempty"`
}

func (c *ServerConfig) secretsHandler(ctx context.Context) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
slog.Debug("Received request", "address", r.RemoteAddr, "url", r.URL)
if r.Method != http.MethodPost {
slog.Debug("Method not allowed", "method", r.Method, "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusMethodNotAllowed)
_, _ = w.Write([]byte("Method not allowed"))
return
}
if r.Header.Get("Content-Type") != "application/json" {
slog.Debug("Unsupported media type", "media-type", r.Header.Get("Content-Type"), "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusUnsupportedMediaType)
_, _ = w.Write([]byte("Unsupported media type"))
return
}
if c.ListenToken != "" && r.Header.Get("Authorization") != "Bearer "+c.ListenToken {
slog.Debug("Unauthorized", "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte("Unauthorized"))
return
}

input := RequestInput{}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
slog.Debug("Unable to read input json", "error", err, "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("Bad request"))
return
}

if input.Parameters == nil {
slog.Debug("No parameters provided", "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("Bad request"))
return
}

_, k8s, err := c.GetClient(input.Parameters)
if err != nil {
slog.Error("Failed to get k8s client", "error", err, "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Internal server error"))
return
}

listOptions := metav1.ListOptions{}

if input.Parameters != nil && input.Parameters.LabelSelector != nil {
labels := []string{}
for key, value := range input.Parameters.LabelSelector {
labels = append(labels, key+"="+value)
}
listOptions.LabelSelector = strings.Join(labels, ",")
slog.Debug("Using label selector", "labelSelector", listOptions.LabelSelector, "address", r.RemoteAddr, "url", r.URL)
}

namespaces, err := k8s.CoreV1().Namespaces().List(ctx, listOptions)
if err != nil {
slog.Error("Failed to list namespaces", "error", err, "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Internal server error"))
return
}

output := ResponseBody{
Output: &ResponseOutput{
Parameters: []*ResponseParameters{},
},
}

for _, ns := range namespaces.Items {
output.Output.Parameters = append(output.Output.Parameters, &ResponseParameters{
Namespace: &ns.Name,
})
}

slog.Debug("Returning response", "address", r.RemoteAddr, "url", r.URL, "output", output)
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(output); err != nil {
slog.Error("Failed to encode response", "error", err, "address", r.RemoteAddr, "url", r.URL)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Internal server error"))
}
}
}
Loading

0 comments on commit 6ec6dbb

Please sign in to comment.